使用Python写入精确格式的CSV文件

2 投票
3 回答
1804 浏览
提问于 2025-04-15 23:18

我在处理一些csv数据文件时遇到了麻烦。这是我项目的一部分。有人建议我使用python的csv读取器来帮助我拆分这些文件,我尝试了一下,取得了一些成功,但还不能用。

这段代码和我之前尝试的有点不同。我其实是想创建一个数组。在原始数据格式中,前7行没有数据,然后每一列包含50个实验,每个实验有4000行,总共大约有200000行。我想做的是把每一列提取出来,单独保存为一个csv文件,每个实验放在自己的列里。所以最终会得到一个包含50列和每种数据类型4000行的数组。这里的代码确实能提取出正确的值,我觉得逻辑是没问题的,但它的拆分方式和我想要的正好相反。我希望分隔符(逗号和空格)不带引号,而元素值要带引号。现在的情况正好相反,元素值没有引号,分隔符却带了引号。我花了好几个小时试图弄明白该怎么做,但一直没有成功。

import csv  

ifile  = open('00_follow_maverick.csv')  
epistemicfile = open('00_follower_maverick_EP.csv', 'w')  

reader = csv.reader(ifile)  

colnum = 0  
rownum = 0  
y = 0  
z = 8   
for column in reader:  
    rownum = 4000 * y + z  
    for element in column:  
        writer = csv.writer(epistemicfile)  
        if y <= 50:  
            y = y + 1  
            writer.writerow([element])  
            writer.writerow(',')  
            rownum = x * y + z  
        if y > 50:  
            y = 0  
            z = z + 1  
            writer.writerow(' ')  
            rownum = x * y + z  
        if z >= 4008:  
            break  

发生了什么:我在原始数据文件中每4000行进行一次迭代,这样我就可以用逗号分隔50个实验。当这里的实验指示器y达到50时,它会重置为实验0,并将z加1,这样就能通过公式4000 * y + z来确定要查看的行。当所有50个实验的行都处理完后,就结束了。问题是我不知道怎么让python把实际值写在引号里,而把分隔符放在引号外。

任何帮助都将非常感谢。如果这个问题听起来很傻,我很抱歉,我没有编程经验,这是我第一次尝试。谢谢。

抱歉,我会尽量让这个问题更清楚。原始的csv文件有几列,每列都是不同的数据集。

原始文件的一个小示例看起来像这样:

column1             column2            column3
exp1data1time1      exp1data2time1     exp1data3time1
exp1data1time2      exp1data2time2     exp1data3time2
exp2data1time1      exp2data2time1     exp2data3time1
exp2data1time2      exp2data2time2     exp2data3time2
exp3data1time1      exp3data2time1     exp3data3time1
exp3data1time2      exp3data2time2     exp3data3time2

所以,实际版本每个新实验有4000行,而不是2行。实际版本有40列,但基本上,原始文件中的数据类型与列号相匹配。我想把每种数据类型或每列分开,保存为单独的csv文件。

这将看起来像这样:

csv file1

exp1data1time1   exp2data1time1   exp3data1time1   
exp1data1time2   exp2data1time2   exp3data1time2

csv file2

exp1data2time1   exp2data2time1   exp3data2time1   
exp1data2time2   exp2data2time2   exp3data2time2

csv file3

exp1data3time1   exp2data3time1   exp3data3time1   
exp1data3time2   exp2data3time2   exp3data3time2

所以,我会把文件中的原始数据移动到一个新列中,每种数据类型保存到自己的文件里。现在我只打算做一个文件,直到我能把不同的实验移动到新文件的不同列中。所以在代码中,上面的例子会把4000变成2。我希望这样更容易理解,如果还不清楚,我会再试一次。

3 个回答

0

归一化代码

#!/usr/bin/python

"""parses a csv file containing a particular data layout and normalizes

    The raw data set is a csv file of the form::

        column1                column2               column3
        exp01data01time01      exp01data02time01     exp01data03time01
        exp01data01time02      exp01data02time02     exp01data03time02

    where there are 40 such columns and the literal column title
    is added as context to the output row

    it is assumed that the columns are comma separated but
    the lexical form of the subcolumns is unspecified.

    Output will consist of a single CSV output stream
    on stdout of the form::

        exp01, time01, data01, column1

    for varying actual values of each field.
"""

import csv
import sys

def split_subfields(s):
    """returns a list of subfields of s
       this function is expected to be re-written to match the actual,
       unspecified lexical structure of s."""
    return [s[0:5], s[5:11], s[11:17]]


def normalise_data(reader, writer):
    """returns a list of the column headings from the reader"""

    # obtain the headings for use in normalization
    names = reader.next()

    # get the data rows, split them out by column, add the column name
    for row in reader:
        for column, datum in enumerate(row):
            fields = split_subfields(datum)
            fields.append(names[column])
            writer.writerow(fields)

def main():
    if len(sys.argv) != 2:
        print  >> sys.stderr,  ('usage: %s input.csv' % sys.argv[0])
        sys.exit(1)

    in_file = sys.argv[1]

    reader = csv.reader(open(in_file))
    writer = csv.writer(sys.stdout)
    normalise_data(reader, writer)

if __name__ == '__main__': main()

这样,当你运行命令 python epistem.py raw_data.csv > cooked_data.csv 时,输出的结果会像下面这样:

exp01,data01,time01,column1
...
exp01,data40,time01,column40
exp01,data01,time02,column1
exp01,data01,time03,column1
...
exp02,data40,time15,column40
0

数据集的标准化

谢谢你提供的例子。你已经了解了我之前描述的背景,也许我可以更清楚地说明。

column1             column2            column3
exp1data1time1      exp1data2time1     exp1data3time1
exp1data1time2      exp1data2time2     exp1data3time2

这些列是上一个人随便弄出来的,也就是说,它们并没有什么重要的信息。当把数据整理成标准形式时,你的数据看起来就像我最初提议的那种组合:

<experiment_number, time, response_number, response>

我怀疑这里的time实际上可能是指“subject_id”或者“trial_number”。把所有不同的response值放在同一个数据集中,可能让你觉得不太合理;确实,根据你想要的输出,我也觉得这样做有点问题。乍一看,你可能会觉得“受试者对椅子的认识属性问题的回答和他们对颜色的元认识信念没有关系”,但这样想是不对的。这些数据是相关的,因为它们有一个共同的实验对象,而自我相关性在社会学分析中是一个重要的概念。

举个例子,你可能会发现受访者A和受访者B的回答是一样的,只不过A的回答因为对标准的理解偏高了一点。这在数据的绝对值上会产生很大的差别,但我希望你能明白,“A和B的认识模型真的不同吗?”这个问题是很重要且有效的。有一种数据建模的方法可以轻松回答这个问题,而你想要的方法则不能。

接下来会提供一些有效的解析代码。

2

如果我每次看到生物、心理或化学数据库都是这种状态,我就能养很多猫:

“每一列包含50个实验,每个实验有4000行,总共大约200000行。我想做的是把每一列变成一个单独的csv文件,每个实验在自己的列里。所以每种数据会变成50列和4000行的数组。”

那我就会有太多猫了。

我甚至没看你的代码,因为你提到的重新整理数据又是一个需要解决的问题。我不怪你,你说自己是个新手,很多同行也会犯同样的错误。刚开始编程的人往往还不懂如何使用数组,结果会写出像这样的变量声明:

integer response01, response02, response03, response04, ...

然后当他们想检查每个响应是否是1时,就会写出非常冗余的代码。我觉得在生物信息学中,这种错误特别诱人,因为它实际上很好地模拟了他们所用的论文符号。不幸的是,纸张模型并不是建模数据的最佳方式。

你应该了解一下为什么数据库规范化会被开发出来,并且成为人们思考结构化数据的主流方式。可能一篇维基百科的文章不够。用我摘录的例子来解释一下我的想法。你的数据由观察组成;换句话说,主要的数据是一个单独的观察。这个观察是有上下文的:它是4000个观察中的一个,而每组观察属于50个实验中的一个。如果你要给每个观察附上上下文,你最终会得到一个看起来像这样的地址方案:

<experiment_number, observation_number, value>

在数据库术语中,这叫做元组,它能够清晰无误地表示你所有的数据。我不确定我是否完全理解你的数据结构,所以可能更像是:

<experiment_number, protocol_number, observation_number, value>

其中协议可能是某种变量处理类型——比如说pH。但请注意,我并没有把协议称为pH,也没有在数据库中这样记录。接下来我需要的是一个辅助表,显示协议的相关参数,例如:

<protocol_number, acidity, temperature, pressure>

现在我们就建立了一个数据库人员喜欢谈论的“关系”;我们也开始对数据进行规范化。如果你需要知道某个协议的pH值,只有一个地方可以找到它,就是协议表的正确行。注意,我把在数据表上看起来很好的数据和观察表分开了,我无法从观察表中看到特定数据的pH值。但没关系,因为如果需要,我可以在我的协议表中查找。这就是“关系连接”,如果需要,我可以把所有不同表中的各种参数合并,重新构建出原始的、无结构的数据表。

希望这个回答对你有帮助。我不确定你的数据来自哪个研究领域,但这些原则适用于从药物试验到采购申请处理的各个领域。请理解,我是为了满足你的请求而提供信息,并没有任何居高临下的意思。如果你还有其他问题,欢迎继续提问。

撰写回答