【问题标题】:Iterating through a CSV and pulling out the last line of the day for each element遍历 CSV 并为每个元素提取当天的最后一行
【发布时间】:2017-07-22 20:50:43
【问题描述】:

我昨天问过这个问题,但没有得到有效的答案,但其中大部分内容不够具体。

我有一个有趣的问题,我不太确定如何最好地解决,遍历一个列表。采用以下格式 -

element, date, unixTime, before, after
CZ, 12/27/07 3:55 PM, 1198788900, 42345, 42346
CZ, 12/27/07 5:30 PM, 1198794600, 42346, 42300
CZ, 12/27/07 7:05 PM,1198800300, 42300, 42000
JB, 12/27/07 7:05 PM,1198800300, 13722, 13500
I, 12/27/2007 7:05 PM, 1198800300, 4475, 4572

我想遍历每个唯一元素和每个日期,并在前后列中获取更改的符号。例如,我想要 2007 年 12 月 27 日的 3 行,用于 CZ JB 和 I。每天有数百万行,有时甚至数千行,我只需要打印一天中每个元素的最后一行。因此,12/27 有多个,可能是数百或数千个“CZ”行。我只需要拉出最后一个,然后打印输出“CZ,12/27/07, 1”或“CZ,12/27/07,-1”。对于 12/27,CZ 的最后一行是负方向,因此它打印 -1。对于 JB、我和所有其他元素,它也会做同样的事情。文件按unix时间排序,元素可以混用。我希望输出如下所示

element, date, direction
CZ, 12/27/07, -1
JB, 12/27/07, -1
I, 12/27/07, 1
CZ, 12/28/07, 1
JB, 12/27/07, -1
I, 12/27/07, -1

试图在 bash、python、perl 甚至类似 awk 之类的东西中找出一种简洁的方式来做这样的事情。对于每一天,它将为当天至少有一条线的每个元素提供一个方向。我考虑了这样做的缓慢方法,例如阅读文件,填充“元素”和“日期”表,然后执行 foreach 并比较每个集合以找到最大的 unix 时间,然后用它打印,但必须有一个更好的方式来运行一次。

【问题讨论】:

  • 请,,向我们展示你所做的。一个问题对 SO 来说是合法的,这很有帮助
  • 当我看到“我想要 last line where ...”的问题时,我倾向于认为:反转文件,然后找到 first i> 行...

标签: python bash perl awk


【解决方案1】:

这似乎是 dictdicts 应用程序。

由于您可以指望用最新的值覆盖该值,因此在 unixtime 中,您可以动态构建一个dict,按日期键入,然后在其中为每个元素添加dicts。

import csv

d = {}
with open('inputfile.csv', 'rb') as f:
    reader = csv.reader(f)
    for row in reader:
        element = row[0]
        date = row[1]
        before = float(row[3])
        after = float(row[4])

        if date not in d:
              d[date] = {}

        if before < after:
              d[date][element] =  1
        else:
              d[date][element] = -1

如果您需要输出必须按日期排序,您可以获取日期并对其进行排序。

from datetime import datetime
dates = [datetime.strptime(date.split(None)[0], '%y/%m/%d') 
         for date d.keys()]
sorted(dates)

否则,您可以按原样获取日期作为字符串。

dates = d.keys()

然后直接写输出文件

with open('outfile.csv', 'wb') as f:
    writer = csv.writer(f)
    writer.writerow(['element', 'date', 'direction'])
    for date in dates:
        for element in d[date]:
            writer.writerow([delement, date, d[date][element]])

【讨论】:

  • 请注意,在第 8 行,您只需要获取日期部分并丢弃时间,就像我在代码的第 5 行中所做的那样。此外,您可以在第 3 行中使用 collections.defaultdict 以避免第 12 和 13 行,您甚至可以使用 (date, element) 的元组作为键将其全部存储在单个字典而不是嵌套字典中。
  • 哦,你的最后 4 行可以写成一个:d[date][element] = 1 if before &lt; after else 1。当您习惯它时,它会使内容更具可读性。
  • @JosepValls:最好先测试自己的解决方案,然后再批评其他人的解决方案
  • @heyiamt: beforeafterrow[3]row[4]
  • @JosepValls 我同意在示例中可以进行大量压缩。我认为更加迂腐一点是谨慎的,但也许这是对 OP 的 python 背景的不公平评估
【解决方案2】:

你也可以考虑使用groupby

如果您的数据已经采用这种格式:

[
['CZ', '12/27/07 3:55 PM', '1198788900', '42345', '42346',],
['CZ', '12/27/07 5:30 PM', '1198794600', '42346', '42300',],
['CZ', '12/27/07 7:05 PM','1198800300', '42300', '42000',],
['JB', '12/27/07 7:05 PM','1198800300', '13722', '13500',],
['I', '12/27/07 7:05 PM', '1198800300', '4475', '4572']
]

那么你可以这样做:

#truncate out the time portion of the second col:
for row in data:
    row[1] = row[1].split(" ")[0]

#sort by symbol and date    
data = sorted(data, key = lambda x: (x[0], int(x[2]))) 
from itertools import groupby

for k, g in groupby(data, lambda x:x[:2]):
    before,after = list(g)[-1][-2:] #extracts the last line.
    k.append( "1" if int(after) > int(before) else "-1" )
    print ",".join(k)

输出如下:

CZ,12/27/07,-1
I,12/27/07,1
JB,12/27/07,-1

【讨论】:

  • OP 提到文件已经排序并且有“数百万”行,所以我避免假设数据已经加载并避免使用它。根据我的经验,如果数据不是已经排序,在处理大文件时,最好在 Python 之外对其进行排序(使用 sort 命令)
【解决方案3】:

Perl(已测试):

use warnings;
use strict;

my ($inputfile, %output) = "input.csv";
open (my $pfile, '<', "$inputfile") or die "Couldn't open file '$inputfile'\n";

<$pfile>; #  SKIPS HEADER LINE, delete if there is no header present

while (my $row = <$pfile>) {
    die "Malformed line: $row" if $row !~ /^\s*(\S+)\s*,\s*(\d\d\/\d\d\/)(\d\d)(\d\d)?\s[^,]*,[^,]*,\s*(\d+)\s*,\s*(\d+)\s*$/;
    my ($element, $date, $before, $after) = ($1, $2.($4 ? $4 : $3), $5, $6);
    $output{$element . $date} = [ $element, $date, $before - $after < 0 ? -1 : 1 ];
}
close $pfile;

print join(',', @$_)."\n" foreach ([ "element", "date", "direction" ], values %output);

【讨论】:

    【解决方案4】:

    在 GNU awk 中(正则表达式 FS)。它只缓冲以前(和当前)的记录,因此它需要排序的输入文件,并且一次大小无关紧要:

    $ awk -F"(, |,| )" -v OFS="," '
    p!=($1 OFS $2) && NR>1 {                                 # when $1 and $2 change
        print p, q                                           # print previous
    }
    {
        p=$1 OFS $2                                          # set previous 
        q=$7-$6
        q=( q ? q/sqrt(q^2) : ( NR==1 ? "direction" : 0 ) )  # determine -1,0,1
    }
    END {
        print p, q                                           # last line handling
    }
    ' file
    
    element,date,direction
    CZ,12/27/07,-1
    JB,12/27/07,-1
    I,12/27/2007,1
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-05
      • 2020-05-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多