合并/平均多个数据文件

时间:2013-12-28 19:34:50

标签: python bash

我有一组数据文件(例如,“data ####。dat”,其中#### = 0001,...,9999)都具有相同x的公共数据结构 - 第一列中的值,然后是具有不同y值的多个列。

data0001.dat:

#A < comment line with unique identifier 'A'
#B1 < this is a comment line that can/should be dropped
1 11 21
2 12 22
3 13 23

data0002.dat:

#A < comment line with unique identifier 'A'
#B2 < this is a comment line that can/should be dropped
1 13 23
2 12 22
3 11 21

它们基本上源自我的程序的不同运行,具有不同的种子,我现在想要将这些部分结果组合成一个共同的直方图,以便保留以“#A”开头的注释行(对于所有文件都相同)以及其他注释行被删除。第一列保留,然后所有其他列应平均所有数据文件:

dataComb.dat:

#A < comment line with unique identifier 'A'
1 12 22 
2 12 22 
3 12 22 

其中12 = (11+13)/2 = (12+12)/2 = (13+11)/222 = (21+23)/2 = (22+22)/2 = (23+21)/2

我已经有一个bash脚本(可能是可怕的代码;但我不是那么经验......)通过在命令行中运行./merge.sh data* > dataComb.dat来完成这项工作。它还会检查所有数据文件是否在第一列中具有相同的列数和相同的值。

merge.sh:

#!/bin/bash

if [ $# -lt 2 ]; then
    echo "at least two files please"
    exit 1;
fi

i=1
for file in "$@"; do
    cols[$i]=$(awk '
BEGIN {cols=0}
$1 !~ /^#/ {
  if (cols==0) {cols=NF}
  else {
    if (cols!=NF) {cols=-1}
  }
}
END {print cols}
' ${file})
    i=$((${i}+1))
done

ncol=${cols[1]}
for i in ${cols[@]}; do
    if [ $i -ne $ncol ]; then
        echo "mismatch in the number of columns"
        exit 1
    fi
done

echo "#combined $# files"
grep "^#A" $1

paste "$@" | awk "
\$1 !~ /^#/ && NF>0 {
  flag=0
  x=\$1
  for (c=1; c<${ncol}; c++) { y[c]=0. }
  i=1
  while (i <= NF) {
    if (\$i==x) {
      for (c=1; c<${ncol}; c++) { y[c] += \$(i+c) }
      i+= ${ncol}
    } else { flag=1; i=NF+1; }
  }
  if (flag==0) {
    printf(\"%e \", x)
    for (c=1; c<${ncol}; c++) { printf(\"%e \", y[c]/$#) }
    printf(\"\n\")
  } else { printf(\"# x -coordinate mismatch\n\") }
}"

exit 0

我的问题是,对于大量数据文件,它会很快变慢,并且在某些时候会导致“太多打开文件”错误。我看到只是一次性粘贴所有数据文件(paste "$@")就是问题,但是分批进行并以某种方式引入临时文件似乎也不是理想的解决方案。在保留调用脚本的方式(即,作为命令行参数传递的所有数据文件)时,我感谢任何帮助以使其更具可伸缩性

我决定在python部分发布这个,因为我经常被告知处理这类问题非常方便。然而,我几乎没有使用python的经验,但也许这是最终开始学习它的机会;)

4 个答案:

答案 0 :(得分:2)

下面附加的代码适用于Python 3.3并产生所需的输出,但有一些小的注意事项:

  • 它从它处理的第一个文件中抓取初始注释行,但是没有费心去检查之后的所有其他文件仍然匹配(即,如果你有几个以#A开头的文件和一个从#C开始,它不会拒绝#C,即使它可能应该)。我主要想说明合并函数如何在Python中工作,并认为添加这种类型的杂项有效性检查最好留作“作业”问题。
  • 它也无需检查行数和列数是否匹配,如果不匹配则可能会崩溃。考虑一下另一个小作业问题。
  • 它将第一个列右侧的所有列打印为浮点值,因为在某些情况下,这就是它们可能是什么。初始列被视为标签或行号,因此将打印为整数值。

您可以像以前一样调用代码;例如,如果您将脚本文件命名为merge.py,则可以执行python merge.py data0001.dat data0002.dat,它会将合并的平均结果打印到stdout,就像使用bash脚本一样。与之前的答案之一相比,代码还具有更大的灵活性:它的编写方式,原则上应该(我实际上没有测试过这一点以确保)能够将文件与任意数量的列合并,而不仅仅是具有正好三列的文件。另一个好处是:它完成后不会保持文件打开; with open(name, 'r') as infile:行是一个Python习语,在脚本从文件读完后会自动导致文件关闭,即使close()从未显式调用过。

#!/usr/bin/env python

import argparse
import re

# Give help description
parser = argparse.ArgumentParser(description='Merge some data files')
# Add to help description
parser.add_argument('fname', metavar='f', nargs='+',
                    help='Names of files to be merged')
# Parse the input arguments!
args = parser.parse_args()
argdct = vars(args)

topcomment=None
output = {}
# Loop over file names
for name in argdct['fname']:
    with open(name, "r") as infile:
        # Loop over lines in each file
        for line in infile:
            line = str(line)
            # Skip comment lines, except to take note of first one that
            # matches "#A"
            if re.search('^#', line):
                if re.search('^#A', line) != None and topcomment==None:
                    topcomment = line
                continue
            items = line.split()
            # If a line matching this one has been encountered in a previous
            # file, add the column values
            currkey = float(items[0])
            if currkey in output.keys():
                for ii in range(len(output[currkey])):
                    output[currkey][ii] += float(items[ii+1])
            # Otherwise, add a new key to the output and create the columns
            else:
                output[currkey] = list(map(float, items[1:]))

# Print the comment line
print(topcomment, end='')
# Get total number of files for calculating average
nfile = len(argdct['fname'])              
# Sort the output keys
skey = sorted(output.keys())
# Loop through sorted keys and print each averaged column to stdout
for key in skey:
    outline = str(int(key))
    for item in output[key]:
        outline += ' ' + str(item/nfile)
    outline += '\n'
    print(outline, end='')

答案 1 :(得分:0)

正如快速检查您使用/使用的文件处理程序的数量,试试这个(unix):

cat /proc/sys/fs/file-nr

这将为您提供(已分配文件处理程序的数量) - (已分配但未使用的文件处理程序的数量) - (文件处理程序的最大数量)---请参阅here

可以在 sysctl.conf 中更改限制(在linux上 - 参见上面的link) - 但这可能不是一个好主意资源管理方面,因此,不是真正的可扩展性。并且,是的,随着更多处理程序被用于打开每个文件(因为它们在shell执行停止/结束之后才关闭),事情开始变慢,并且最终在没有更多处理程序可用时失败。

一个可能的解决方案可以包含Python/SciPy/Pandas和一个简单的数据库。有很棒的文档和大量的社区支持。与您的帖子密切相关的示例是here。关于Pandas接口的小帖子和链接here的数据库。

我没试过,但我会试一试:

对于数据库,您可以使用类似pandas io.sql模块的内容来创建每个dat文件的有用表示(可能使用A#标头作为每个表的标识符)。然后可以通过任何数量的方法操纵数据,例如, glued。这不保留您要求的./merge.sh data* > dataComb.dat功能,但是一个简单的python命令行脚本可能会处理所有步骤以获取数据并按照您的意愿进行处理。

我认为这将是学习曲线,但它可以为未来的可扩展性/灵活性带来回报。

答案 2 :(得分:0)

您似乎很难同时打开太多文件。您似乎已经找到了如何处理剩余的处理(即根据唯一ID对文件进行排序并访问单个.dat文件中包含的值),因此我将只关注此问题< / p>

在处理多个来源时,常见的诀窍是要记住,您不需要一次拥有所有值来计算平均值。您所需要的只是您添加的值的总和和数量。

我不熟悉awk语法,所以我会写伪代码。

  1. 创建与您的数据结构匹配的表sum。假设sum[x][y]保留列x和行y的值。该表最初用零填充。
  2. 设置计数器n = 0
  3. 打开第一个文件。我将跳过您似乎已处理的处理部分,因此请说data包含您提取的值。该访问类似于sum
  4. 所描述的访问权限
  5. 为每个sumsum[x][y] += data[x][y]
  6. 将值添加到x表:y
  7. 关闭文件。
  8. 递增计数器:n += 1
  9. 重复步骤3到6,直到您处理完所有文件
  10. 为每个sum[x][y] = sum[x][y] / nx值计算平均值y
  11. 你明白了! sum现在包含您正在寻找的平均值。
  12. 此算法处理任意数量的文件,并且在任何给定时间只打开一个文件。

答案 3 :(得分:0)

你可以尝试这段代码,主要思想是迭代地读取文件,并用每个数字中第二个和第三个值的计数和总和来更新字典对象,祝你好运!

#first you get the paths for all the dat files:

import os
dat_dir=r'c:\dat_dir'
our_files=[path for os.path.join(dat_dir,f) for f in os.listdir(dat_dir)]

#then you iterate over them and update a dictionary object with the results for each file:

dict_x_values={}
for f in our_files:
    fopen=open(f,'r')
    for line in fopen:
        line=line.strip('\n')
        split=[int(v) for v in line.split()]
        if len(split)==3:
            key=split[0]
            if dict_x_values.has_key(key):
                second_count,second_sum=dict_x_values[key][0] #the second value in the row
                second_count+=1 #we increment the count
                second_sum+=split[1] #we increment the sum
                third_count,third_sum=dict_x_values[key][1] #the third value in the row
                third_count+=1
                third_sum+=split[2]
                dict_x_values[key]=[[second_count,second_sum],[third_count,third_sum]]
            else:
                dict_x_values[key]=[[1,split[1]],[1,split[1]]] #if the dictionary doesn't have the left x-value, we initialize it
    fopen.close()


#Then we write our output combined file

comb_open=open('comb_dat.txt','w')


for key in dict_x_values:
    second_count,second_sum=dict_x_values[key][0] #the second value in the row
    third_count,third_sum=dict_x_values[key][1] #the third value in the row
    second_avg=float(second_sum)/second_count
    third_avg=float(third_sum)/third_count
    line='%s\t%s\t%s'%(key,second_avg,third_avg)

comb_open.close()