Pandas:如何用平均值填充缺失的数据?

3

我每隔5秒从远程设备读取一些数据。

它们被保存为:

 2018-01-01 00:00:00    2
 2018-01-01 00:00:05    3
 2018-01-01 00:00:10    3
 2018-01-01 00:00:15    2
 2018-01-01 00:00:20    3
 2018-01-01 00:00:25    4
 2018-01-01 00:00:30    3
 2018-01-01 00:00:35    2
 2018-01-01 00:00:40    4
 2018-01-01 00:00:45    5
 2018-01-01 00:00:50    3
 2018-01-01 00:00:55    3

遗憾的是,通信并不总是最好的,有时候通信无法正常工作。

在这种情况下,远程设备将尽快提供cumulative value(累积值)的数据。

之前的数据可以保存为:

 2018-01-01 00:00:00     2
 2018-01-01 00:00:05     3
 2018-01-01 00:00:10     3
 .......... 00:00:15  missing...
 .......... 00:00:20  missing...
 .......... 00:00:25  missing...
 2018-01-01 00:00:30    12      <--- sum of the last 4 readings 
 2018-01-01 00:00:35     2
 .......... 00:00:40  missing...
 .......... 00:00:45  missing...
 2018-01-01 00:00:50    15      <--- sum of the last 3 readings
 2018-01-01 00:00:55     3

我需要填充所有缺失的行,并将原始数据中的峰值用在峰值上计算出的平均值来移除。

重采样很容易:

 2018-01-01 00:00:00      2
 2018-01-01 00:00:05      3
 2018-01-01 00:00:10      3
 2018-01-01 00:00:15    NaN
 2018-01-01 00:00:20    NaN
 2018-01-01 00:00:25    NaN
 2018-01-01 00:00:30     12
 2018-01-01 00:00:35      2
 2018-01-01 00:00:40    NaN
 2018-01-01 00:00:45    NaN
 2018-01-01 00:00:50     15
 2018-01-01 00:00:55      3

但是如何填充NaN并消除峰值呢?

我查看了asfreqresample的各种方法,但在这种情况下,它们中的任何一个(bfillffill)都没有用。

最终结果应该是:

 2018-01-01 00:00:00      2
 2018-01-01 00:00:05      3
 2018-01-01 00:00:10      3
 2018-01-01 00:00:15      3 <--- NaN filled with mean = peak 12/4 rows
 2018-01-01 00:00:20      3 <--- NaN filled with mean
 2018-01-01 00:00:25      3 <--- NaN filled with mean
 2018-01-01 00:00:30      3 <--- peak changed
 2018-01-01 00:00:35      2
 2018-01-01 00:00:40      5  <--- NaN filled with mean = peak 15/3 rows
 2018-01-01 00:00:45      5  <--- NaN filled with mean
 2018-01-01 00:00:50      5  <--- peak changed
 2018-01-01 00:00:55      3

我用于测试的数据框:

import numpy as np
import pandas as pd

time = pd.date_range(start='2021-01-01', freq='5s', periods=12)
read_data = pd.Series([2, 3, 3, np.nan, np.nan, np.nan, 12, 2, np.nan, np.nan, 15, 3], index=time).dropna()

read_data.asfreq("5s")

我有一个基本但不是特别快的解决方案:生成完整的日期时间系列,将完整的日期时间列与您的数据合并,然后您就知道哪些日期时间缺失了。然后使用“if else”和“flag”来决定何时应该获得平均值。使用“for循环”来实现这一点。 - Jeremy
2个回答

1
一种方法:

m = (read_data.isna() | read_data.shift(fill_value= 0).isna()).astype(int)
read_data = read_data.bfill() / m.groupby(m.ne(m.shift()).cumsum()).transform('count').where(m.eq(1), 1)

输出:
2021-01-01 00:00:00    2.0
2021-01-01 00:00:05    3.0
2021-01-01 00:00:10    3.0
2021-01-01 00:00:15    3.0
2021-01-01 00:00:20    3.0
2021-01-01 00:00:25    3.0
2021-01-01 00:00:30    3.0
2021-01-01 00:00:35    2.0
2021-01-01 00:00:40    5.0
2021-01-01 00:00:45    5.0
2021-01-01 00:00:50    5.0
2021-01-01 00:00:55    3.0
Freq: 5S, dtype: float64

完整示例:

import numpy as np
import pandas as pd

time = pd.date_range(start='2021-01-01', freq='5s', periods=12)
read_data = pd.Series([2, 3, 3, np.nan, np.nan, np.nan, 12, 2, np.nan, np.nan, 15, 3], index=time).dropna()

read_data = read_data.asfreq("5s")

m = (read_data.isna() | read_data.shift(fill_value= 0).isna()).astype(int)
read_data = read_data.bfill() / m.groupby(m.ne(m.shift()).cumsum()).transform('count').where(m.eq(1), 1)

我认为你的解决方案很完美,但是 shift 带来了一个小问题:如果 NaN 的组被单个值分隔开,算法就无法工作。尝试在 date_range 中更改 periods=11 并删除原始数据框中 12 后面的 2,以了解我的意思。 - Alex Poca

1
这可以通过将缺失值(在重新采样后)与其对应的峰值分段(分组)在一起,形成一个单独的组,进行反向填充,然后计算每个组的平均值来实现:
>>> read_data = read_data.to_frame(name='val').assign(idx=range(len(read_data)))
>>> read_data = read_data.asfreq('5s').bfill()
>>> read_data = read_data/read_data.groupby('idx').transform(len)
>>> read_data.drop('idx', axis=1, inplace=True)
>>> read_data.val
2021-01-01 00:00:00    2.0
2021-01-01 00:00:05    3.0
2021-01-01 00:00:10    3.0
2021-01-01 00:00:15    3.0
2021-01-01 00:00:20    3.0
2021-01-01 00:00:25    3.0
2021-01-01 00:00:30    3.0
2021-01-01 00:00:35    2.0
2021-01-01 00:00:40    5.0
2021-01-01 00:00:45    5.0
2021-01-01 00:00:50    5.0
2021-01-01 00:00:55    3.0
Freq: 5S, Name: val, dtype: float64

解释:

首先将您的原始序列转换为数据框,并引入另一列 idx,它将唯一标识每一行作为单个组:

>>> read_data = read_data.to_frame(name='val').assign(idx=range(len(read_data)))
>>> read_data
                      val  idx
2021-01-01 00:00:00   2.0    0
2021-01-01 00:00:05   3.0    1
2021-01-01 00:00:10   3.0    2
2021-01-01 00:00:30  12.0    3
2021-01-01 00:00:35   2.0    4
2021-01-01 00:00:50  15.0    5
2021-01-01 00:00:55   3.0    6

对于缺失的值进行重新采样,然后使用峰值填充缺失的值:
>>> read_data = read_data.asfreq('5s').bfill()
>>> read_data
                      val  idx
2021-01-01 00:00:00   2.0  0.0
2021-01-01 00:00:05   3.0  1.0
2021-01-01 00:00:10   3.0  2.0
2021-01-01 00:00:15  12.0  3.0
2021-01-01 00:00:20  12.0  3.0
2021-01-01 00:00:25  12.0  3.0
2021-01-01 00:00:30  12.0  3.0
2021-01-01 00:00:35   2.0  4.0
2021-01-01 00:00:40  15.0  5.0
2021-01-01 00:00:45  15.0  5.0
2021-01-01 00:00:50  15.0  5.0
2021-01-01 00:00:55   3.0  6.0

现在您可以看到,填充后的值与它们的峰值(具有相同的 idx)属于同一组。
因此,groupby idx 并仅将每个组的值除以其长度。删除 idx 列:

>>> read_data = read_data/read_data.groupby('idx').transform(len)
>>> read_data.drop('idx', axis=1, inplace=True)
>>> read_data
                     val
2021-01-01 00:00:00  2.0
2021-01-01 00:00:05  3.0
2021-01-01 00:00:10  3.0
2021-01-01 00:00:15  3.0
2021-01-01 00:00:20  3.0
2021-01-01 00:00:25  3.0
2021-01-01 00:00:30  3.0
2021-01-01 00:00:35  2.0
2021-01-01 00:00:40  5.0
2021-01-01 00:00:45  5.0
2021-01-01 00:00:50  5.0
2021-01-01 00:00:55  3.0

1
非常感谢您的解决方案。讲解得非常清楚明白。 - Alex Poca
1
很高兴能帮助 :) - Ank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接