迭代CSV并为每个元素提取当天的最后一行

时间:2017-03-02 22:20:08

标签: python bash perl awk

我昨天问了这个问题并且得不到合适的答案,但其中很多都没有具体说明。

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

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

我想迭代,为每个唯一元素和每个日期迭代,并获得前/后列中更改的符号。例如,对于CZ JB和I,我想要3行12/27/2007。有数百万行,有时每天有数千行,我只需要打印当天每个元素的最后一行。所以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时间然后用它打印,但是必须有一个相反,更好的方式来完成它。

4 个答案:

答案 0 :(得分:0)

这似乎是dict dict的申请。

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

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]])

答案 1 :(得分:0)

您也可以考虑使用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

答案 2 :(得分:0)

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);

答案 3 :(得分:0)

在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