根据第二列值拆分 CSV 文件

根据第二列值拆分 CSV 文件

我正在使用 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。)

那么,让我们看看。

  1. 我们收到您的意见allpeople.csv
  2. 我们运行sqlite3 people.sqlite,它为我们提供了一个简洁的小 shell,我们可以在其中编写以下命令:
  3. CREATE TABLE "people" ("id" INTEGER UNIQUE, "age" INTEGER);Enter,它创建一个包含两列“id”和“age”的新表,这两列都是整数。 “id”甚至保证是唯一的 - 如果我们尝试让两个条目具有相同的“id”,您会收到投诉。
  4. .import --csv "allpeople.csv" "people"Enter,它将读取 CSV“allpeople”并将其加载到我们刚刚创建的表“people”中

现在我们已经准备好了数据。(无论我们从数据库中进行多少次选择,我们只需要执行一次。)
乐趣从这里开始:

  1. .mode csvEnter,这会将输出模式设置为 CSV
  2. .output oldpeople.csvEnter,它告诉 sqlite 将输出写入文件“oldpeople.csv”(包括您要求的标头)
  3. SELECT * FROM "people" WHERE "age" >= 60;Enter,你猜对了,它选择包含年龄至少为 60 岁的人的所有行,并将结果放入oldpeople.csv
  4. .output youngpeople.csvEnterSELECT * FROM "people" WHERE "age" < 60;Enter无需进一步解释
  5. .quitEnter退出 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 上轻松获得宇宙存储库。


  1. 例如,在 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 变量overunder.如果您喜欢对输出文件名进行硬编码,则可以将它们作为双引号字符串直接嵌入到脚本中(但请注意,由于拼写错误,在代码中重复自己是错误的常见来源,通常最好使用变量或经常重复使用的值的常量):

$ 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

相关内容