2

我正在尝试重写一些我认为非常低效(更不用说不优雅)的旧 bash 脚本并使用一些可怕的管道......也许具有真正 Python 技能的人可以给我一些指示......

该脚本使用多个临时文件......我认为另一件事是一种糟糕的风格,可能可以避免......

它本质上是INPUT-FILE通过首先从顶部剪掉一定数量的行(丢弃标题)来进行操作的。
然后它拉出其中一列并:

  • 计算数量raws = N
  • 从这个单列文件中抛出所有重复的条目(我使用sort -u -n FILE > S-FILE)。

之后,我创建了一个从 1 到 N 的连续整数索引,并INPUT-FILE使用 paste 命令将这个新的索引列粘贴到原始索引列中。
然后我的 bash 脚本为我们写入 S-FILE 的值生成百分比等级。
我相信 Python 的利用scipy.stats,而在 bash 中,我确定 S-FILE 中每个唯一条目的重复行数(dupline),然后计算per-rank=$((100*($counter+$dupline/2)/$length)),其中 $length= FILE 的长度而不是 S-FILE。然后,我会将结果打印到一个单独的 1 列文件中(并且重复相同的每个等级的次数与我们有重复线的次数一样多)。
然后,我会将这个带有百分位等级的新列粘贴回 INPUT-FILE(因为我将按用于计算百分位等级的列对 INPUT-FILE 进行排序 - 结果中的所有内容都会完美排列)。

在此之后,它进入下面的丑陋......

sort -o $INPUT-FILE $INPUT-FILE

awk 'int($4)>2000' $INPUT-FILE | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 2000-$INPUT-FILE

diff $INPUT-FILE 2000-$INPUT-FILE | sed '/^[0-9][0-9]*/d; s/^. //; /^---$/d' | awk 'int($4)>1000' | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 1000-$INPUT-FILE

cat 2000-$INPUT-FILE 1000-$INPUT-FILE | sort > merge-$INPUT-FILE

diff merge-$INPUT-FILE $INPUT-FILE | sed '/^[0-9][0-9]*/d; s/^. //; /^---$/d' | awk 'int($4)>500' | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 500-$INPUT-FILE

rm merge-$INPUT-FILE

从本质上讲,这是一种非常不优雅的 bash 执行以下操作的方式:

  1. 从 $INPUT-FILE 中随机选择 500 行,其中第 4 列中的值大于 2000 并将其写入文件 2000-$INPUT-FILE
  2. 对于 $INPUT-FILE 中的所有 REMAINING 行,随机选择第 4 列中的值大于 1000 的 500 行并将其写入文件 1000-$INPUT-FILE
  3. 对于 1) 和 2) 之后 $INPUT-FILE 中的所有 REMAINING 行,随机选择第 4 列中的值大于 500 的 500 行并将其写入文件 500-$INPUT-FILE

再一次,我希望有人能帮助我把这个丑陋的管道改造成蟒蛇之美!:) 谢谢!

4

2 回答 2

1

评论中的两个关键点:

(A) 该文件是约 50k 行约 100 个字符。足够小,可以舒适地放入现代台式机/服务器/笔记本电脑系统的内存中。

(B) 作者的主要问题是关于如何跟踪已经选择的行,并且不要再次选择它们。

我建议三个步骤。

(1) 浏览文件,制作满足每个条件的行号的三个单独的列表——称它们为 u、v、w。这些列表可能有 500 多行,并且可能包含重复项,但我们将在步骤 (2) 中解决这些问题。

u = []
v = []
w = []

with open(filename, "r") as f:
    for linenum, line in enumerate(f):
        x = int(line.split()[3])
        if x > 2000:
            u.append(x)
        if x > 1000:
            v.append(x)
        if x > 500:
            w.append(x)

(2) 选择行号。您可以使用内置的 Random.sample() 从总体中选择 k 个元素的样本。我们要删除之前选择的元素,因此要跟踪集合中的这些元素。(“选择”集合是一个集合而不是一个列表,因为测试“如果 x 未在选择中”对于一个集合来说是 O(log(n)),但对于一个列表来说是 O(n)。将其更改为一个列表和如果您精确测量时间,您会看到速度变慢,尽管对于“仅”50k 个数据点/500 个样本/3 个类别的数据集来说,这可能不是一个明显的延迟。)

import random
rand = random.Random()       # change to random.Random(1234) for repeatable results

chosen = set()
s0 = rand.sample(u, 500)
chosen.update(s0)
s1 = rand.sample([x for x in v if x not in chosen], 500)
chosen.update(s1)
s2 = rand.sample([x for x in w if x not in chosen], 500)
chosen.update(s2)

(3) 再次遍历输入文件,将编号为 s0 的行放入第一个输出文件,将编号为 s1 的行放入第二个输出文件,将编号为 s2 的行放入第三个输出文件。这在任何语言中都很简单,但这里有一个使用 Python“成语”的实现:

linenum2sample = dict([(x, 0) for x in s0]+[(x, 1) for x in s1]+[(x, 2) for x in s2])

outfile = [open("-".join(x, filename), "w") for x in ["2000", "1000", "500"]]

try:
    with open(filename, "r") as f:
        for linenum, line in enumerate(f):
            s = linenum2sample.get(linenum)
            if s is not None:
                outfile[s].write(line)
finally:
    for f in outfile:
        f.close()
于 2013-03-26T01:45:50.303 回答
0

把它分成简单的部分。

  1. 如果标题不可用,则使用 csv.DictReader 或 csv.reader 读取文件。在遍历这些行时,检查第 4 列的值并将这些行插入到列表字典中,其中字典键类似于“gt_2000”、“gt_1000”、“gt_500”。

  2. 遍历您的字典键并为每个键创建一个文件并执行 500 个循环,对于每次迭代,使用 random.randint(0, len(the_list)-1) 获取列表的随机索引,将其写入文件,然后从列表中删除该索引处的项目。如果任何存储桶中的项目少于 500 个,那么这将需要更多一点。

于 2013-03-27T02:23:34.137 回答