我有一组数据文件(例如,“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)/2
和22 = (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的经验,但也许这是最终开始学习它的机会;)
答案 0 :(得分:2)
下面附加的代码适用于Python 3.3并产生所需的输出,但有一些小的注意事项:
您可以像以前一样调用代码;例如,如果您将脚本文件命名为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语法,所以我会写伪代码。
sum
。假设sum[x][y]
保留列x
和行y
的值。该表最初用零填充。n = 0
data
包含您提取的值。该访问类似于sum
。sum
和sum[x][y] += data[x][y]
值x
表:y
n += 1
sum[x][y] = sum[x][y] / n
和x
值计算平均值y
sum
现在包含您正在寻找的平均值。此算法处理任意数量的文件,并且在任何给定时间只打开一个文件。
答案 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()