【问题标题】:Splitting Rows in csv on several header rows在多个标题行上拆分 csv 中的行
【发布时间】:2016-01-23 05:54:41
【问题描述】:

我对python很陌生,所以请温柔。

我有一个 .csv 文件,以这种格式向我报告,所以我对此无能为力:

ClientAccountID   AccountAlias   CurrencyPrimary    FromDate
         SomeID      SomeAlias          SomeCurr    SomeDate
        OtherID     OtherAlias         OtherCurr   OtherDate
ClientAccountID   AccountAlias   CurrencyPrimary    AssetClass
         SomeID      SomeAlias          SomeCurr     SomeClass
        OtherID     OtherAlias         OtherCurr     OtherDate
      AnotherID   AnotherAlias       AnotherCurr   AnotherDate

我在 python 中使用 csv 包,所以我有:

with open(theFile, 'rb') as csvfile:
    theReader = csv.DictReader(csvfile, delimiter = ',')

据我了解,它创建了字典“theReader”。如何将此字典子集化为多个字典,并按原始 csv 文件中的标题行将它们拆分?是否有一种简单、优雅、非循环的方法来创建字典列表(甚至是字典字典,以帐户 ID 作为键)?这有意义吗?

哦。请注意标题行不相等,但标题行将始终以“ClientAccountID”开头。

感谢@codie,我现在使用以下内容将 csv 拆分为几个字典,基于使用 '\t' 分隔符。

with open(theFile, 'rb') as csvfile:
    theReader = csv.DictReader(csvfile, delimiter = '\t')

但是,我现在将整个标题行作为键,将其他行作为值。我该如何进一步拆分?

感谢下面的@Benjamin Hodgson,我有以下几点:

from csv import DictReader
from io import BytesIO

stringios = []

with open('file.csv', 'r') as f:
    stringio = None
    for line in f:
        if line.startswith('ClientAccountID'):
            if stringio is not None:
                stringios.append(stringio)
            stringio = BytesIO()
        stringio.write(line)
        stringio.write("\n")
    stringios.append(stringio)

data = [list(DictReader(x.getvalue(), delimiter=',')) for x in stringios]

如果我在 stringios 中打印第一个项目,我会得到我所期望的。它看起来像一个单独的 csv。但是,如果我打印数据中的第一项,使用下面,我会得到一些奇怪的东西:

for row in data[0]:
    print row

返回:

{'C':'U'}
{'C':'S'}
{'C':'D'}
...

所以看起来它正在拆分每个字符,而不是使用逗号分隔符。

【问题讨论】:

  • 使用制表符分隔符,(\t) 不是逗号分隔符
  • 哦。多么简单。然后如何创建子词典?
  • for row in theReader: do something() 其中 row 是给定行中值的字典。 Python 在幕后为你做了所有的魔法。
  • 您需要从 csv 文件中呈现几行实际的行,以便我们给您一个正确的答案。如果情况是您有一个 csv 文件,该文件在三(或七)行的块中具有多个不同的 id,那么您在读取文件时需要做一些魔术。当前的示例模棱两可...
  • 你需要编一些假数据才能更好地知道格式是什么。取前 10 行或其他内容并混淆数据。

标签: python csv dictionary


【解决方案1】:

如果我正确理解了您的问题,您就有一个包含多个表格的 CSV 文件。表格由标题行分隔,标题行始终以字符串 "ClientAccountID" 开头。

因此,工作是将 CSV 文件读入字典列表列表。列表中的每个条目对应于 CSV 文件中的一个表。

我会这样做:

  1. 将包含多个表的单个 CSV 文件拆分为多个文件,每个文件包含一个表。 (这些文件可能在内存中。)通过查找以 "ClientAccountID" 开头的行来执行此操作。
  2. 使用DictReader 将每个文件读入字典列表。

下面是一些将文件读入StringIOs 列表的代码。 (StringIO 是一个内存文件。它通过将字符串包装到类似文件的接口中来工作。

from csv import DictReader
from io import StringIO

stringios = []

with open('file.csv', 'r') as f:
    stringio = None
    for line in f:
        if line.startswith('ClientAccountID'):
            if stringio is not None:
                stringio.seek(0)
                stringios.append(stringio)
            stringio = StringIO()
        stringio.write(line)
        stringio.write("\n")
    stringio.seek(0)
    stringios.append(stringio)

如果我们遇到以'ClientAccountID' 开头的行,我们将当前的StringIO 放入列表并开始写入新的。完成后,记得将最后一个也添加到列表中。 在您使用stringio.seek(0) 写入StringIO 后,不要忘记(就像我在此答案的早期版本中所做的那样)。

现在可以直接循环遍历 StringIOs 以获取字典表。

data = [list(DictReader(x, delimiter='\t')) for x in stringios]

对于列表stringios 中的每个类文件对象,创建一个DictReader 并将其读入列表。

如果您的数据太大而无法放入内存,则修改此方法并不难。使用生成器而不是列表并逐行进行处理。

【讨论】:

  • 我收到一个错误:stringio.write(line) TypeError: unicode argument expected, got 'str' 是的,你完全理解我想要做什么。谢谢。
  • 这个答案是 Python 3 代码。根据该错误消息,我猜您使用的是 2.7。尝试将StringIO 更改为BytesIO
  • 是的,我使用的是 2.7。该信息可能是相关的......道歉。仍然遇到问题,因为它似乎可以工作,但“数据”在完成时是空的。我正在努力。
  • 表示没有以'ClientAccountID'开头的行。行首是否有空格?如果是这样,请尝试 line.strip().startswith('ClientAccountID') 而不是 line.startswith(...)。 (另外,我刚刚发现并修复了答案中的一个错误;)
  • 抱歉 - 刚刚更新。看起来分隔符没有被识别?或者它在每个字符处拆分,而不是在逗号处。
【解决方案2】:

如果您的数据不是逗号或制表符分隔的,您可以使用str.split,您可以将其与itertools.groupby 结合使用来分隔标题和行:

from itertools import groupby, izip, imap

with open("test.txt") as f:
    grps, data = groupby(imap(str.split, f), lambda x: x[0] == "ClientAccountID"), []
    for k, v in grps:
        if k:
            names = next(v)
            vals = izip(*next(grps)[1])
            data.append(dict(izip(names, vals)))

from pprint import pprint as pp

pp(data)

输出:

[{'AccountAlias': ('SomeAlias', 'OtherAlias'),
  'ClientAccountID': ('SomeID', 'OtherID'),
  'CurrencyPrimary': ('SomeCurr', 'OtherCurr'),
  'FromDate': ('SomeDate', 'OtherDate')},
 {'AccountAlias': ('SomeAlias', 'OtherAlias', 'AnotherAlias'),
  'AssetClass': ('SomeClass', 'OtherDate', 'AnotherDate'),
  'ClientAccountID': ('SomeID', 'OtherID', 'AnotherID'),
  'CurrencyPrimary': ('SomeCurr', 'OtherCurr', 'AnotherCurr')}]

如果是制表符分隔,只需更改一行:

with open("test.txt") as f:
    grps, data = groupby(csv.reader(f, delimiter="\t"), lambda x: x[0] == "ClientAccountID"), []
    for k, v in grps:
        if k:
            names = next(v)
            vals = izip(*next(grps)[1])
            data.append(dict(izip(names, vals)))

【讨论】:

  • 这很好。我不知道这个 lambda 东西,但它非常有用。另外,你可以在第二个中使用 str.split 吗?有没有办法将不同的分隔符(\t)传递给它?
  • 另外,查看 imap 的文档,它说 imap(function, *iterables),暗示文件 f 是可迭代的?我是 python 新手,我只是好奇,但是在传递文件时,它被解释为一组可迭代的行?如果是这样,您迭代行,str.split 它们,然后按第一个术语是 ClientAccountID 的行对它们进行分组?我想我理解正确,但似乎......不寻常(?)该文件只是一组可迭代的行?还是(相当)正常?谢谢!
  • @lukehawk,你可以传递任何你想要的分隔符,但在地图示例中你需要使用map(lambda x: x.split("whatever"),我们可以传递str.split,因为它是可调用的,所以我们不需要lambda ,是的,一个文件对象返回它自己的迭代器,所以当你迭代它时,你将一次得到一行,imap 将每一行拆分为任何空白,然后每次我们 if k 为 True 我们有一行与第一个column == "ClientAccountID" 因此我们在 groupby 对象上调用 next 以获取所有行,直到具有“ClientAccountID”的下一行或文件末尾
猜你喜欢
  • 2016-08-08
  • 2015-08-12
  • 2022-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多