【问题标题】:What's the most elegant, Pythonic way to deduplicate two sequential records in a CSV and retaining one record?在 CSV 中删除两个连续记录并保留一个记录的最优雅的 Pythonic 方法是什么?
【发布时间】:2016-06-02 23:55:01
【问题描述】:

我正在尝试对 CSV 中的记录进行重复数据删除。我不认为自己是 Python 新手或编写 ETL 脚本的新手。我已经尽职尽责并搜索了 S.O.页面,并且不要认为这个问题可以通过使用 SET 来解决(就像大多数重复数据删除问题一样)。

我的目标是:对于所有ORIG等于前一行ORIG的行,在ORIG相等的两行中,删除SEQ_TIME == 0的那一行。

正如 Python 格言所说,“应该有一种——最好只有一种——明显的方式来做到这一点。”我编写的代码我相信可以实现这一点,但任何人都会告诉你这是非常不符合 Pythonic 的。 CSV 数据看起来像这样,下面是我的单独结果 CSV。满足条件的行以黄色突出显示,便于比较。

CSV 文本格式的数据:

TRAIN#,SEQ#,ORIG,DEP,DEST,ARR,SEQ_TIME
A21,9,BPK,0.582986111,X66,0.584375,2
A21,10,X66,0.584375,CNLEMOYN,0.586805556,3.5
A21,11,CNLEMOYN,0.586805556,SMT,0.590972222,6
A21,12,SMT,0.590972222,,0.590972222,0
A21,13,SMT,0.590972222,CNCANAL,0.591666667,1
A21,14,CNCANAL,0.591666667,MEWILSPR,0.594791667,4.5
A21,15,MEWILSPR,0.594791667,,0.594791667,0
A21,16,MEWILSPR,0.594791667,MELEMONT,0.6,7.5
A21,17,MELEMONT,0.6,,0.6,6.5
A21,18,MELEMONT,0.6,MELOCKPO,0.605208333,0
A21,19,MELOCKPO,0.605208333,,0.605208333,0
A21,20,MELOCKPO,0.605208333,XUD,0.60625,2.5
A21,21,XUD,0.60625,JOL,0.607638889,2

我认为实现目标的(非 Pythonic)代码如下。

import csv

f = open("my_data.csv", "r")
reader = csv.reader(f, lineterminator="\n")
header = reader.next()

# Dict comprehension so we can refer to each column by index or name.
hdict = {value:index for index, value in enumerate(header)}

# Data is converted to a 2-D list, since I do other stuff with it later.
data = [row for row in reader]

# Main (un-pythonic) solution.
result = []
try:
    i = 0
    while True:
        row1 = data[i]
        row2 = data[i+1] # Will cause an IndexError on the last row.
        if row1[hdict["ORIG"]] == row2[hdict["ORIG"]]:
            if float(row1[hdict["SEQ_TIME"]]):
                result.append(row1)
            elif float(row2[hdict["SEQ_TIME"]]):
                result.append(row2)
            else:
                raise AssertionError("Two sequential rows with equivalent ORIG cannot both have SEQ_TIME == 0.")
            i += 1 # Force-skips to row3 in the next iteration, since row1 & row2 are handled above.
        else:
            result.append(row1)
        i += 1 # I'm brute-forcing a loop with a manual index.
except IndexError:    
    result.append(data[-1]) # Handle the last row.

# Write results to some other CSV.
g = open("my_results.csv", "w")
writer = csv.writer(g, lineterminator="\n")
writer.writerow(header)

for row in result:
    writer.writerow(result)

f.close()
g.close()

虽然 Python 中的 while True: break 习语很常见并且(我相信)编码草率,但 try: while True: go on forever: except IndexError 习语确实很糟糕。有没有更简单、更优雅的方式来完成这项任务,比如简单的for 循环?

我追求的一个想法是在for 循环中迭代每一行时使用可迭代对象来控制光标:

data_iterable = iter(data)

for row in data_iterable:
    row1 = row[:]
    row2 = data_iterable.next() # Controlling the cursor here.
    if row1[hdict["ORIG"]] == row2[hdict["ORIG"]]:
        if float(row1[hdict["SEQ_TIME"]]):
            result.append(row1)
        else:
            result.append(row2)
        # The AssertionError check can be omitted.
    else:
        result.append(row1) # If nothing unusual...
        result.append(row2) # append both rows.

这里的问题是这段代码只处理偶数个重复而错过了奇数个重复。

或者,我们可以遍历数据两次,根据 SEQ# 之类的 ID 标记我们想要保留在 keep_these_rows 列表中的行。然后在第二遍时,只将那些行附加到结果中?但这对我来说似乎同样笨拙,而且必然慢 2 倍。

大家有什么更好的解决方案吗?

注意事项:

  • hdict 是结合csv.readercsv.DictReader 功能的简单方法,因此您可以按名称引用行,例如row[hdict["ORIG"]] 或索引,例如row[2]
  • 我阅读了@DSM 的一篇文章,其中提到了itertools.GroupBy 函数作为竞争者。这对我们有什么好处吗?

谢谢!

【问题讨论】:

  • 为什么not very useful发的是图片而不是文字?
  • 这个更适合codereview.stackexchange.com
  • @DSM 添加了 CSV 作为文本,并在 itertools.groupby 帖子中提到了您的姓名。谢谢。
  • @chepner 我想你是对的。我不会在 codereview.stackexchange.com 版块中重新发布此帖子,以免遭到各种成员的“双重海报”报复,但如果有人可以顶撞此帖子,请务必使用。

标签: python list csv duplicates


【解决方案1】:

如果您要压缩的组都是连续的,那么您是对的,itertools.groupby 可能有用。假设(比如说)我们想要保留 SEQ_TIME == 0 的情况,如果它们是一个组的唯一成员,或者如果有 SEQ_TIME == 0 的三个连续条目,我们可以做类似(Python 3 csv open style) :

import csv
import itertools

with open("dedup.csv", newline="") as fp_in, open("dedup_out.csv", "w", newline="") as fp_out:
    reader = csv.DictReader(fp_in)
    writer = csv.DictWriter(fp_out, reader.fieldnames)
    writer.writeheader()
    for key, group in itertools.groupby(reader, key=lambda row: row["ORIG"]):
        group = list(group)
        if len(group) == 2:
            group = [row for row in group if not float(row["SEQ_TIME"]) == 0]
        writer.writerows(group)

这给了我

TRAIN#,SEQ#,ORIG,DEP,DEST,ARR,SEQ_TIME
A21,9,BPK,0.582986111,X66,0.584375,2
A21,10,X66,0.584375,CNLEMOYN,0.586805556,3.5
A21,11,CNLEMOYN,0.586805556,SMT,0.590972222,6
A21,13,SMT,0.590972222,CNCANAL,0.591666667,1
A21,14,CNCANAL,0.591666667,MEWILSPR,0.594791667,4.5
A21,16,MEWILSPR,0.594791667,MELEMONT,0.6,7.5
A21,17,MELEMONT,0.6,,0.6,6.5
A21,20,MELOCKPO,0.605208333,XUD,0.60625,2.5
A21,21,XUD,0.60625,JOL,0.607638889,2

可以根据需要调整组条件。如果您知道永远不会有任何 SEQ_TIME=0 案例您想保留,代码可能会变得更加简单,但这应该为您提供一个起点。

【讨论】:

    猜你喜欢
    • 2020-05-14
    • 2012-04-12
    • 1970-01-01
    • 2019-12-20
    • 2015-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多