在多索引数据框中填充缺失的时间值

Filling missing time values in a multi-indexed dataframe

问题和我想要的

我有一个数据文件,其中包含从多个传感器异步读取的时间序列。基本上对于我文件中的每个数据元素,我都有一个传感器 ID 和读取它的时间,但我并不总是每次都有所有传感器,并且读取时间间隔可能不均匀。类似于:

ID,time,data
0,0,1
1,0,2
2,0,3
0,1,4
2,1,5  # skip some sensors for some time steps
0,2,6
2,2,7
2,3,8
1,5,9  # skip some time steps
2,5,10

重要说明实际的time列是日期时间类型。

我想要的是能够在传感器不存在的任何时间步为每个传感器零阶保持(前向填充)值,并设置为零或回填任何未读取的传感器在最早的时间步骤。我想要的是一个看起来像是从以下位置读取的数据框:

ID,time,data
0,0,1
1,0,2
2,0,3
0,1,4
1,1,2  # ID 1 hold value from time step 0
2,1,5
0,2,6
1,2,2  # ID 1 still holding
2,2,7
0,3,6  # ID 0 holding
1,3,2  # ID 1 still holding
2,3,8
0,5,6  # ID 0 still holding, can skip totally missing time steps
1,5,9  # ID 1 finally updates
2,5,10
到目前为止

Pandas 次尝试

我初始化我的数据框并设置我的索引:

df = pd.read_csv(filename, dtype=np.int)
df.set_index(['ID', 'time'], inplace=True)

我试着把事情搞砸:

filled = df.reindex(method='ffill')

之类的带有传递给 index 关键字参数的各种值,如 df.index['time'] 等。这总是会引发错误,因为我传递了一个无效的关键字参数,或者对数据帧不可见。我认为它没有识别出我要查找的数据是 "missing".

我也试过:

df.update(df.groupby(level=0).ffill())

level=1 基于 ,但我再次没有看到数据框的明显变化,我想是因为我目前没有任何我希望我的价值观去的地方。

到目前为止的 Numpy 尝试

我在 numpy 和非整数索引方面运气不错,使用类似的东西:

data = [np.array(df.loc[level].data) for level in df.index.levels[0]]
shapes = [arr.shape for arr in data]
print(shapes)
# [(3,), (2,), (5,)]
data = [np.array([arr[i] for i in np.linspace(0, arr.shape[0]-1, num=max(shapes)[0])]) for arr in data]
print([arr.shape for arr in data])
# [(5,), (5,), (5,)]

但这有两个问题:

  1. 它让我脱离了 pandas 世界,我现在必须手动维护我的传感器 ID、时间索引等以及我的特征向量(实际的 data 列不是只有一列,但来自传感器套件的大量值。
  2. 考虑到实际数据集的列数和大小,在我的真实示例中实现这将是笨拙且不优雅的。我更喜欢 pandas.
  3. 中的一种方式

申请

最终这只是训练递归神经网络的数据清理步骤,对于每个时间步我都需要提供一个始终具有相同结构的特征向量(每个传感器 ID 的一组测量值时间步长)。

感谢您的帮助!

这是一种方法,使用 reindexcategory

df.time=df.time.astype('category',categories =[0,1,2,3,4,5])
new_df=df.groupby('time',as_index=False).apply(lambda x : x.set_index('ID').reindex([0,1,2])).reset_index()
new_df['data']=new_df.groupby('ID')['data'].ffill()
new_df.drop('time',1).rename(columns={'level_0':'time'})
Out[311]: 
    time  ID  data
0      0   0   1.0
1      0   1   2.0
2      0   2   3.0
3      1   0   4.0
4      1   1   2.0
5      1   2   5.0
6      2   0   6.0
7      2   1   2.0
8      2   2   7.0
9      3   0   6.0
10     3   1   2.0
11     3   2   8.0
12     4   0   6.0
13     4   1   2.0
14     4   2   8.0
15     5   0   6.0
16     5   1   9.0
17     5   2  10.0

您可以拥有每个传感器最后读数的字典。您必须选择一些初始值;最合乎逻辑的选择可能是将最早阅读的内容回填到更早的时间。填充 last_reading 词典后,您可以按时间对所有阅读进行排序,为每次阅读更新词典,然后根据词典填写行。因此,在您初始化 last_reading 字典后:

last_time = readings[1][time]
for reading in readings:
   if reading[time] > last_time:
      for ID in ID_list:
         df.loc[last_time,ID] = last_reading[ID]
      last_time = reading[time]
   last_reading[reading[ID]] = reading[data]
#the above for loop doesn't update for the last time
#so you'll have to handle that separately
for ID in ID_list:
    df.loc[last_time,ID] = last_reading[ID]
    last_time = reading[time]

这假设您对每个 time/sensor 对只有一个阅读,并且 'readings' 是按时间排序的字典列表。它还假设 df 具有不同的传感器作为列和不同的时间作为索引。否则,请根据需要调整代码。您还可以通过一次更新整行而不是使用 for 循环来进一步优化它,但我不想处理以确保我的 Pandas 语法正确。

虽然在应用程序中,您可能希望数据框中的每个单元格不是数字,而是最后一个值和读取时间的元组,因此将 last_reading[reading[ID]] = reading[data] 替换为 last_reading[reading[ID]] = [reading[data],reading[time]]。然后,您的神经网络可以根据数据的年龄来决定如何对数据进行加权。

我得到它来处理以下内容,我认为这对于像这样的任何情况都非常普遍,其中您要为其填充值的时间索引是具有两个索引的多索引中的第二个:

# Remove duplicate time indices (happens some in the dataset, pandas freaks out).
df = df[~df.index.duplicated(keep='first')]

# Unstack the dataframe and fill values per serial number forward, backward.
df = df.unstack(level=0)
df.update(df.ffill())  # first ZOH forward
df.update(df.bfill())  # now back fill values that are not seen at the beginning

# Restack the dataframe and re-order the indices.
df = df.stack(level=1)
df = df.swaplevel()

这让我得到了我想要的东西,尽管如果有人知道这样做的好方法,我很乐意能够保留重复的时间条目。

如果对于特定应用而言,从零开始看不见的值更可取,您也可以使用 df.update(df.fillna(0)) 而不是回填。

我将上面的代码块放在一个名为 clean_df 的函数中,该函数将数据帧作为参数并 returns 清理后的数据帧。