使用Numpy计算数据集的平均值,考虑缺失数据

5 投票
3 回答
5373 浏览
提问于 2025-04-15 16:11

我有大约十个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 个回答

0

一种方法是遍历每个数据集的每一行,把某一列的值添加到一个存储在字典里的数组中,字典的键是时间索引。然后,你再遍历这个字典,计算每个数组的平均值。

不过,这种方法效率不是特别高。另一种选择是找出最长的数组,遍历这个数组,然后查询其他数据集,创建一个临时数组来计算平均值。这样可以省去对字典的第二次遍历。

2

编辑:我修改了我的方法,放弃了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]]
3

为什么不直接使用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]。

撰写回答