使用Numpy计算数据集的平均值,考虑缺失数据
我有大约十个CSV格式的数据集。每个数据集的每一列代表系统运行的一个方面,比如可用的内存、CPU使用率、打开的TCP连接等等。每一行则包含这些列在某一时刻的值。
这些数据集是在同一次测试的不同运行中捕获的。每个数据集的行数不一定相同(也就是说,有些测试运行得比其他的长)。
我想生成一个新的CSV文件,表示在所有数据集中某个时间点和某一列的“平均”值。理想情况下,某个数据集中缺失的值应该被忽略。不过,如果需要的话,可以假设缺失的值和最后一个已知值相同,或者是该行已知值的平均值。
这是一个简化的例子:
+---------------+ +---------------+ +---------------+
| Set 1 | | Set 2 | | Average |
+---+-----+-----+ +---+-----+-----+ +---+-----+-----+
| t | A | B | | t | A | B | | t | A | B |
+---+-----+-----+ +---+-----+-----+ +---+-----+-----+
| 1 | 10 | 50 | | 1 | 12 | 48 | | 1 | 11 | 49 |
| 2 | 13 | 58 | | 2 | 7 | 60 | | 2 | 10 | 59 |
| 3 | 9 | 43 | | 3 | 17 | 51 | => | 3 | 13 | 47 |
| 4 | 14 | 61 | | 4 | 12 | 57 | | 4 | 13 | 59 |
| : | : | : | | : | : | : | | : | : | : |
| 7 | 4 | 82 | | 7 | 10 | 88 | | 7 | 7 | 86 |
+---+-----+-----+ | 8 | 15 | 92 | | 8 | 15 | 92 |
| 9 | 6 | 63 | | 9 | 6 | 63 |
+---+-----+-----+ +---+-----+-----+
我刚开始接触numpy,专门为了这个项目学习的。有什么好的方法可以做到这一点吗?对于行数相同的数据集(我通过截断较长的数据集来强制实现这一点),我只需要这样做:
d_avg = sum(dsets) / float(len(dsets))
这里的“dsets”是一个包含每个CSV文件数据的ndarray列表。这种方法很好,但我不想丢弃较长运行的数据。
我也可以把较短的运行调整到最长的长度,但所有新增加的字段都会填充“NoneType”。这样后续操作在添加(例如)一个浮点数和一个NoneType时就会出错。
有什么建议吗?
3 个回答
一种方法是遍历每个数据集的每一行,把某一列的值添加到一个存储在字典里的数组中,字典的键是时间索引。然后,你再遍历这个字典,计算每个数组的平均值。
不过,这种方法效率不是特别高。另一种选择是找出最长的数组,遍历这个数组,然后查询其他数据集,创建一个临时数组来计算平均值。这样可以省去对字典的第二次遍历。
编辑:我修改了我的方法,放弃了scipy.nanmean,改用了掩码数组。
如果你在任何时候不明白代码在做什么,首先可以尝试加一些print语句来查看输出。如果还是不明白,随时可以问我;我会尽力解释。关键的部分是如何合并t值。(这部分是用numpy数组的searchsorted方法完成的。)
玩弄numpy让我觉得,只有当数据集变得相当大时,它的速度优势才会显现(可能每个数据集至少需要1万行数据)。否则,纯Python的解决方案可能写起来更简单,而且速度也更快。
以下是我使用的玩具数据集:
% cat set1
1, 10, 50
2, 13, 58
3,9,43
4,14,61
7, 4, 82
% cat set2
1, 12, 48
2, 7, 60
3,17,51
4,12,57
7,10,88
8,15,92
9,6,63
这是代码:
#!/usr/bin/env python
import numpy as np
filenames=('set1','set2') # change this to list all your csv files
column_names=('t','a','b')
# slurp the csv data files into a list of numpy arrays
data=[np.loadtxt(filename, delimiter=',') for filename in filenames]
# Find the complete list of t-values
# For each elt in data, elt[a,b] is the value in the a_th row and b_th column
t_values=np.array(list(reduce(set.union,(set(elt[:,0]) for elt in data))))
t_values.sort()
# print(t_values)
# [ 1. 2. 3. 4. 7. 8. 9.]
num_rows=len(t_values)
num_columns=len(column_names)
num_datasets=len(filenames)
# For each data set, we compute the indices of the t_values that are used.
idx=[(t_values.searchsorted(data[n][:,0])) for n in range(num_datasets)]
data2=np.ma.zeros((num_rows,num_columns,num_datasets))
for n in range(num_datasets):
data2[idx[n],:,n]=data[n][:,:]
data2=np.ma.masked_equal(data2, 0)
averages=data2.mean(axis=-1)
print(averages)
# [[1.0 11.0 49.0]
# [2.0 10.0 59.0]
# [3.0 13.0 47.0]
# [4.0 13.0 59.0]
# [7.0 7.0 85.0]
# [8.0 15.0 92.0]
# [9.0 6.0 63.0]]
为什么不直接使用numpy的ma(掩码数组)模块呢?
maxLen = reduce(lambda a,b : max(a, b.shape[0]),
dSets, 0)
all = N.ma.zeros((maxLen,)+ dSets[0].shape[1:] + (len(dSets),),
dtype=float) # set the dtype to whatever
all.mask = True
for i, set in enumerate(dSets):
all.mask[:len(set),...,i] = False
all[:len(set),...,i] = set
mean = all.mean(axis=-1)
当然,这样做的前提是你能保证每一行的时间在所有数组中都是一样的,也就是说,对于所有的i和j,set[i,0]都应该等于set[j,0]。