如何按字段将单个CSV文件切分成多个较小的文件?

4

我有一个来自世界银行千年发展目标的大型数据集,格式为CSV。数据显示如下:

Country Code   Country Name   Indicator
ABW            Aruba          % Forest coverage
ADO            Andorra        % Forest coverage
AFG            Afghanistan    % Forest coverage
...
ABW            Aruba          % Literacy rate
ADO            Andorra        % Literacy rate
AFG            Afghanistan    % Literacy rate
...
ABW            Aruba          % Another indicator
ADO            Andorra        % Another indicator
AFG            Afghanistan    % Another indicator

文件目前为8.2MB。我将编写一个Web界面来处理这些数据,并希望按国家对数据进行切片,以便可以通过ajax请求加载每个国家的单独CSV文件。
我不知道如何使用任何工具或编程语言来实现这一点。虽然我最擅长Python,但我并不一定需要它。我不需要完整的脚本,只需要指导如何解决这个问题。
我正在使用的原始数据源位于此处:http://duopixel.com/stack/data.csv
4个回答

4
你可以使用Python的csv模块itertools.groupby
以下示例已在Python 2.7.1上进行了测试。
编辑:更新答案以考虑问题中添加的新信息。
import csv, itertools as it, operator as op

csv_contents = []
with open('yourfile.csv', 'rb') as fin:
  dict_reader = csv.DictReader(fin)   # default delimiter is comma
  fieldnames = dict_reader.fieldnames # save for writing
  for line in dict_reader:            # read in all of your data
    csv_contents.append(line)         # gather data into a list (of dicts)

# input to itertools.groupby must be sorted by the grouping value 
sorted_csv_contents = sorted(csv_contents, key=op.itemgetter('Country Name'))

for groupkey, groupdata in it.groupby(sorted_csv_contents, 
                                      key=op.itemgetter('Country Name')):
  with open('slice_{:s}.csv'.format(groupkey), 'wb') as fou:
    dict_writer = csv.DictWriter(fou, fieldnames=fieldnames)
    dict_writer.writeheader()         # new method in 2.7; use writerow() in 2.6-
    dict_writer.writerows(groupdata)

其他注意事项:

  • 您可以使用常规的csv读取器和写入器,但DictReader和DictWriter很好用,因为您可以按名称引用列。
  • 在读取或写入.csv文件时,始终使用“b”标志,因为在Windows上,这会影响如何处理行结束符。
  • 如果有任何不清楚的地方,请让我知道,我会进一步解释!

4

一句话概括:

awk -F "," 'NF>1 && NR>1 {print $0 >> ("data_" $1 ".csv"); close("data_" $1 ".csv")}' data.csv

这将创建名为data_ABW的新文件,其中包含相应信息。 NR> 1 部分跳过标题行。 然后,对于每一行,它会将整个行( $0 )附加到名为Data_$1的文件中,其中$1被替换为该行第一列中的文本。 最后,close语句确保没有太多打开的文件。 如果您没有那么多国家,则可以摆脱此问题并显着提高命令的速度。
回答@Lenwood下面的评论,要在每个输出文件中包含标题,可以执行以下操作:
awk -F "," 'NR==1 {header=$0}; NF>1 && NR>1 {if(! files[$1]) {print header >> ("data_" $1 ".csv"); files[$1]=1}; print $0 >> ("data_" $1 ".csv"); close("data_" $1 ".csv")}' data.csv

你可能需要转义感叹号...新加入的第一部分NR==1 {header=$0};将输入文件的第一行存储为变量header。然后,另一个新加入的部分if(! files[$1]) ... files[$1]=1};使用关联数组files跟踪是否将标题放入给定文件中,如果没有,则将其放入其中。

请注意,这会追加文件,因此,如果这些文件已经存在,它们将被添加到其中。因此,如果您在主文件中获取了新数据,则可能需要在再次运行此命令之前删除那些其他文件。

(如果不明显,如果您希望文件的名称像data_Aruba,则可以将$1更改为$2。)


1
谢谢您的回复,我在您的建议后进行了修改。感谢,它完美地运行,并且满足我的需求,速度也不慢。 - methodofaction
1
+1,对我非常有效。是否可以在每个生成的子CSV中包含标题行? - Lenwood
1
非常好的观点。我已经编辑了答案,包括第二个版本来实现这一点。(我还添加了NF>1,它排除了只有一个字段的行,以便原始问题中的行不会被处理。) - Mike
对我来说,只有子CSV文件的第一个文件有标题。我错过了什么? - Mitendra
明白了! 如果(!files[$1])... files[$1]=1}; 在我的情况下,$1没有改变,但是其他字段改变了。 - Mitendra
显示剩余2条评论

2

使用pandas Python数据分析库非常简单:

from pandas.io.parsers import read_csv

df = read_csv(input_file, header=1, sep='\t', index_col=[0,1,2])
for (country_code, country_name), group in df.groupby(level=[0,1]):
    group.to_csv(country_code+'.csv')

结果

$ for f in *.csv ; do echo $f; cat $f; echo; done

ABW.csv
Country Code,Country Name,Indicator
ABW,Aruba,% Forest coverage
ABW,Aruba,% Literacy rate
ABW,Aruba,% Another indicator

ADO.csv
Country Code,Country Name,Indicator
ADO,Andorra,% Forest coverage
ADO,Andorra,% Literacy rate
ADO,Andorra,% Another indicator

AFG.csv
Country Code,Country Name,Indicator
AFG,Afghanistan,% Forest coverage
AFG,Afghanistan,% Literacy rate
AFG,Afghanistan,% Another indicator

1
在shell脚本中。
首先,awk '{print $1}' | sort | uniq > code.lst将给你一个国家代码列表。然后,您可以通过迭代国家代码并使用grep选择匹配该代码的youfilename.csv的所有行。
for c in `ls code.lst` do
   grep $c youfilename.csv > youfilename_$c.csv
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接