我正在使用 Ubuntu,我想根据第二列(年龄)中的值将我的 csv 文件拆分为两个 csv 文件。第一个文件适用于 60 岁以下 (<60) 的患者,第二个文件适用于 60 岁以上 (>=) 的患者。例如,如果我有以下输入:
id,age
1,65
2,63
3,5
4,55
5,78
期望的输出是:
文件下:
id,age
3,5
4,55
文件覆盖:
id,age
1,65
2,63
5,78
我已尝试以下代码,但它删除了标题(列名称),如何避免这种情况?
awk -F ',' '($2>=60){print}' file.csv > file_over.csv
输入文件大约有50k行(lines)。
答案1
我会在一个环境中执行所有此类过滤操作,该环境已经将我的数据理解为表的内容,而不仅仅是字符行。
由于您似乎正在进行过滤,因此理想情况下该环境应该具有某种结构化语言,您可以使用它来查询表。
输入SQL,s结构化的q尤里我语言,发明用于处理患者数据库等。
有一个工具可以为数据库提供这样的 SQL 接口,而数据库甚至不必存在于磁盘上sqlite。 (很可能它甚至已经安装在你的 ubuntu 上。否则,它非常非常小,可以使用 来安装sudo apt install sqlite3
。)
那么,让我们看看。
- 我们收到您的意见
allpeople.csv
- 我们运行
sqlite3 people.sqlite
,它为我们提供了一个简洁的小 shell,我们可以在其中编写以下命令: CREATE TABLE "people" ("id" INTEGER UNIQUE, "age" INTEGER);
Enter,它创建一个包含两列“id”和“age”的新表,这两列都是整数。 “id”甚至保证是唯一的 - 如果我们尝试让两个条目具有相同的“id”,您会收到投诉。.import --csv "allpeople.csv" "people"
Enter,它将读取 CSV“allpeople”并将其加载到我们刚刚创建的表“people”中
现在我们已经准备好了数据。(无论我们从数据库中进行多少次选择,我们只需要执行一次。)
乐趣从这里开始:
.mode csv
Enter,这会将输出模式设置为 CSV.output oldpeople.csv
Enter,它告诉 sqlite 将输出写入文件“oldpeople.csv”(包括您要求的标头)SELECT * FROM "people" WHERE "age" >= 60;
Enter,你猜对了,它选择包含年龄至少为 60 岁的人的所有行,并将结果放入oldpeople.csv
.output youngpeople.csv
EnterSELECT * FROM "people" WHERE "age" < 60;
Enter无需进一步解释.quit
Enter退出 sqlite。
当然,您也可以将上面的这些命令写入文本文件“commands.sql”,并使用sqlite3 people.sqlite < commands.sql
.
请注意,“people.sqlite”现在仍然包含一个比“allpeople.csv”中的数据库更快可读、更紧凑且更灵活的数据库。我通常避免做任何CSV 上的统计、数学或分析工作 – 恕我直言,这不是正确的格式。 SQL 非常方便,您可以做更多有趣的事情,特别是如果您有多个表,或者有两个以上列。
例如,如果您的数据有另一列“性别”和一列“体重”,那么 SQL 可以轻松地在一个干净的SELECT
语句中选出所有 18 至 20 岁之间的重度超重男性。如果您有另一个将诊断映射到人员表中的“id”的表,您甚至可以专门找到这些患有糖尿病的 18 至 20 岁重度男性。(您可以大概在 awk 中做同样的事情,但在某些时候确实会变得麻烦且缓慢。)
我有点喜欢 SQLite,因为它实际上作为数据交换格式工作得很好 - 与 CSV 不同,很少有工具就其编码、分隔、引用、转义、空白和标题达成一致。 SQLite 实际上是一种定义的存储格式,CSV 更多的是一个粗略的想法,通常,如果你只是用逗号分隔它们,事情就会起作用。
数据分析中使用的常见编程语言通常内置有 sqlite3 接口 - python3 的标准库包含该sqlite3
模块,Perl 有DBD::SQLite
(除其他外),R 有library(RSQLite)
,C/C++ 有本机接口......
答案2
您使用 awk 的方法基本上是前提是文件不包含高级 CSV 功能,例如字段中的引号逗号。您可能应该将测试更改为$2+0<60
和$2+0>=60
以确保比较是数字而不是词汇,即使 的值$2
被解析为字符串1。
要在这两种情况下发出标题行,您需要添加一个为第一条记录返回 true 的测试。您可以{print}
在此上下文中完全省略,因为这是默认操作。所以
$ awk -F ',' 'NR==1 || $2+0<60' file.csv
id,age
3,5
4,55
和
$ awk -F ',' 'NR==1 || $2+0>=60' file.csv
id,age
1,65
2,63
5,78
如果您的文件不符合简单的 CSV 标准,那么其他一些选项来自csvsql
基于 Python 的 csvkit:
$ csvsql --query 'SELECT * FROM file WHERE age >= 60' file.csv
id,age
1,65
2,63
5,78
或者磨坊主:
$ mlr --csv filter '$age >= 60' file.csv
id,age
1,65
2,63
5,78
这两个csvkit
包都miller
可以从 Ubuntu 上轻松获得宇宙存储库。
- 例如,在 C 语言环境中,
a
按字典顺序大于,6
因此您可能会观察到($2>=60){print}
包含标题行,而不($2<60){print}
包含标题行
答案3
使用 的awk
内置输出重定向一次性分割文件:
$ awk -F, -v over=file_over.csv \
-v under=file_under.csv \
'NR==1 { print > over; print > under ; next };
$2 < 60 { print > under ; next };
{ print > over }' file.csv
重定向的awk
工作方式与 shell 中的重定向类似 - 主要区别在于 awk>
仅在第一次写入脚本时截断输出文件(因此不需要>>
后续输出行,除非您希望脚本附加到现有文件) 。
上面的一行代码允许您使用 awk 变量over
和under
.如果您喜欢对输出文件名进行硬编码,则可以将它们作为双引号字符串直接嵌入到脚本中(但请注意,由于拼写错误,在代码中重复自己是错误的常见来源,通常最好使用变量或经常重复使用的值的常量):
$ awk -F, 'NR==1 {
print > "file_over.csv";
print > "file_under.csv" ;
next
};
$2 < 60 { print > "file_under.csv" ; next };
{ print > "file_over.csv" }' file.csv```
或者将它们设置在 BEGIN 块中:
$ awk -F, '
BEGIN {
over = "file_over.csv";
under = "file_under.csv";
};
NR==1 { print > over; print > under ; next };
$2 < 60 { print > under ; next };
{ print > over }' file.csv
输入和输出文件:
$ head file*.csv
==> file.csv <==
id,age
1,65
2,63
3,5
4,55
5,78
==> file_over.csv <==
id,age
1,65
2,63
5,78
==> file_under.csv <==
id,age
3,5
4,55
答案4
使用任何 awk:
$ awk -F',' '
NR==1 { print > "file_under"; print > "file_over"; next }
{ print > ( "file_" ($2 < 60 ? "under" : "over") ) }
' file
$ head file_under file_over
==> file_under <==
id,age
3,5
4,55
==> file_over <==
id,age
1,65
2,63
5,78
或者,如果您愿意,这将产生相同的输出,而不必在代码中重复输出文件名:
awk -F',' '
BEGIN { split("file_over,file_under",out) }
NR==1 { for (i in out) print > out[i]; next }
{ print > out[($2 < 60)+1] }
' file