【问题标题】:pandas plot time-series with minimized gapspandas 绘制间隙最小的时间序列
【发布时间】:2016-11-29 01:52:11
【问题描述】:

我最近开始探索 pandas 的深度,并希望可视化一些包含间隙的时间序列数据,其中一些间隙相当大。一个例子mydf:

             timestamp       val
0  2016-07-25 00:00:00  0.740442
1  2016-07-25 01:00:00  0.842911
2  2016-07-25 02:00:00 -0.873992
3  2016-07-25 07:00:00 -0.474993
4  2016-07-25 08:00:00 -0.983963
5  2016-07-25 09:00:00  0.597011
6  2016-07-25 10:00:00 -2.043023
7  2016-07-25 12:00:00  0.304668
8  2016-07-25 13:00:00  1.185997
9  2016-07-25 14:00:00  0.920850
10 2016-07-25 15:00:00  0.201423
11 2016-07-25 16:00:00  0.842970
12 2016-07-25 21:00:00  1.061207
13 2016-07-25 22:00:00  0.232180
14 2016-07-25 23:00:00  0.453964

现在我可以通过df1.plot(x='timestamp').get_figure().show() 绘制我的 DataFrame,并且沿 x 轴的数据将被插值(显示为一条线):

我想要的是:

  • 数据部分之间的可见间隙
  • 为不同的间隙长度提供一致的间隙宽度
  • 可能是轴上的某种形式的标记,有助于阐明执行了一些时间跳跃的事实。

研究这件事我遇到了

这通常接近我所追求的,但前一种方法会简单地将间隙留在绘制的图形之外,而后者则留在我想避免的大间隙中(想想甚至可能跨越一个几天)。

由于第二种方法可能更接近,我尝试通过以下方式使用我的时间戳列作为索引:

mydf2 = pd.DataFrame(data=list(mydf['val']), index=mydf[0])

这让我可以通过重新索引来填补NaN 的空白(想知道是否有更简单的解决方案来实现这一点)

mydf3 = mydf2.reindex(pd.date_range('25/7/2016', periods=24, freq='H'))

导致:

                          val
2016-07-25 00:00:00  0.740442
2016-07-25 01:00:00  0.842911
2016-07-25 02:00:00 -0.873992
2016-07-25 03:00:00       NaN
2016-07-25 04:00:00       NaN
2016-07-25 05:00:00       NaN
2016-07-25 06:00:00       NaN
2016-07-25 07:00:00 -0.474993
2016-07-25 08:00:00 -0.983963
2016-07-25 09:00:00  0.597011
2016-07-25 10:00:00 -2.043023
2016-07-25 11:00:00       NaN
2016-07-25 12:00:00  0.304668
2016-07-25 13:00:00  1.185997
2016-07-25 14:00:00  0.920850
2016-07-25 15:00:00  0.201423
2016-07-25 16:00:00  0.842970
2016-07-25 17:00:00       NaN
2016-07-25 18:00:00       NaN
2016-07-25 19:00:00       NaN
2016-07-25 20:00:00       NaN
2016-07-25 21:00:00  1.061207
2016-07-25 22:00:00  0.232180
2016-07-25 23:00:00  0.453964

从这里开始,我可能需要将缺失数据的连续条目减少到一定限制,以固定数字(代表我的间隙宽度)并对这些条目的索引值做一些事情,以便它们以不同的方式绘制,但我得到了我想在这里迷路了,因为我不知道如何实现这样的目标。

在修补时,我想知道是否有更直接和优雅的方法,如果有人对此有更多了解,可以为我指明正确的方向,我将不胜感激。

提前感谢您的任何提示和反馈!

### 附录###

发布我的问题后,我遇到了另一个有趣的idea postend by Andy Hayden,这似乎很有帮助。他使用列来保存差异与时间增量的比较结果。在对布尔结果的 int 表示形式执行 cumsum() 后,他使用 groupby() 将每个 ungapped-series 的条目聚集到 DataFrameGroupBy-object 中。

由于这是前段时间写的,pandas 现在返回 timedelta-objects,因此应该与另一个 timedelta-object 进行比较(基于上面的 mydf 或重新索引后的 df2通过mydf2['timestamp'] = mydf2.index将其索引复制到现在列):

from datetime import timedelta
myTD = timedelta(minutes=60)
mydf['nogap'] = mydf['timestamp'].diff() > myTD
mydf['nogap'] = mydf['nogap'].apply(lambda x: 1 if x else 0).cumsum() 
## btw.: why not "... .apply(lambda x: int(x)) ..."?
dfg = mydf.groupby('nogap')

我们现在可以遍历 DataFrameGroup 来获取未锁定的系列,然后对它们进行一些操作。我的 pandas/mathplot-skills 太不成熟了,但我们可以将组元素绘制成子图吗?也许这样沿时间轴的不连续性可以以某种方式表示(以中断的轴线等形式)?

piRSquared 的答案已经得出了一个非常有用的结果,唯一缺少的是沿时间轴的更引人注目的视觉反馈,即两个值之间发生了间隙/时间跳跃。

也许对于分组的部分,间隙表示的宽度可以更可配置?

【问题讨论】:

    标签: python pandas matplotlib plot


    【解决方案1】:

    我建立了一个新系列并绘制了它。这不是超级优雅!但我相信你会得到你想要的。

    设置

    这样做是为了到达你的起点

    from StringIO import StringIO
    import pandas as pd
    
    text = """          timestamp       val
    2016-07-25 00:00:00   0.740442
    2016-07-25 01:00:00   0.842911
    2016-07-25 02:00:00  -0.873992
    2016-07-25 07:00:00  -0.474993
    2016-07-25 08:00:00  -0.983963
    2016-07-25 09:00:00   0.597011
    2016-07-25 10:00:00  -2.043023
    2016-07-25 12:00:00   0.304668
    2016-07-25 13:00:00   1.185997
    2016-07-25 14:00:00   0.920850
    2016-07-25 15:00:00   0.201423
    2016-07-25 16:00:00   0.842970
    2016-07-25 21:00:00   1.061207
    2016-07-25 22:00:00   0.232180
    2016-07-25 23:00:00   0.453964"""
    
    s1 = pd.read_csv(StringIO(text),
                     index_col=0,
                     parse_dates=[0],
                     engine='python',
                     sep='\s{2,}').squeeze()
    
    s1
    
    timestamp
    2016-07-25 00:00:00    0.740442
    2016-07-25 01:00:00    0.842911
    2016-07-25 02:00:00   -0.873992
    2016-07-25 07:00:00   -0.474993
    2016-07-25 08:00:00   -0.983963
    2016-07-25 09:00:00    0.597011
    2016-07-25 10:00:00   -2.043023
    2016-07-25 12:00:00    0.304668
    2016-07-25 13:00:00    1.185997
    2016-07-25 14:00:00    0.920850
    2016-07-25 15:00:00    0.201423
    2016-07-25 16:00:00    0.842970
    2016-07-25 21:00:00    1.061207
    2016-07-25 22:00:00    0.232180
    2016-07-25 23:00:00    0.453964
    Name: val, dtype: float64
    

    每小时重新采样。 resample 是一个延迟方法,这意味着它希望您在之后传递另一个方法,以便它知道要做什么。我用mean。对于您的示例,这并不重要,因为我们正在以更高的频率进行采样。如果你在乎,就去看看。

    s2 = s1.resample('H').mean()
    
    s2
    
    timestamp
    2016-07-25 00:00:00    0.740442
    2016-07-25 01:00:00    0.842911
    2016-07-25 02:00:00   -0.873992
    2016-07-25 03:00:00         NaN
    2016-07-25 04:00:00         NaN
    2016-07-25 05:00:00         NaN
    2016-07-25 06:00:00         NaN
    2016-07-25 07:00:00   -0.474993
    2016-07-25 08:00:00   -0.983963
    2016-07-25 09:00:00    0.597011
    2016-07-25 10:00:00   -2.043023
    2016-07-25 11:00:00         NaN
    2016-07-25 12:00:00    0.304668
    2016-07-25 13:00:00    1.185997
    2016-07-25 14:00:00    0.920850
    2016-07-25 15:00:00    0.201423
    2016-07-25 16:00:00    0.842970
    2016-07-25 17:00:00         NaN
    2016-07-25 18:00:00         NaN
    2016-07-25 19:00:00         NaN
    2016-07-25 20:00:00         NaN
    2016-07-25 21:00:00    1.061207
    2016-07-25 22:00:00    0.232180
    2016-07-25 23:00:00    0.453964
    Freq: H, Name: val, dtype: float64
    

    好的,所以你还想要同样大小的间隙。这有点棘手。我使用ffill(limit=1) 仅填充每个空白的一个空格。然后我拿了s2 的一部分,这个前向填充的东西不为空。这为每个间隙提供了一个空值。

    s3 = s2[s2.ffill(limit=1).notnull()]
    
    s3
    
    timestamp
    2016-07-25 00:00:00    0.740442
    2016-07-25 01:00:00    0.842911
    2016-07-25 02:00:00   -0.873992
    2016-07-25 03:00:00         NaN
    2016-07-25 07:00:00   -0.474993
    2016-07-25 08:00:00   -0.983963
    2016-07-25 09:00:00    0.597011
    2016-07-25 10:00:00   -2.043023
    2016-07-25 11:00:00         NaN
    2016-07-25 12:00:00    0.304668
    2016-07-25 13:00:00    1.185997
    2016-07-25 14:00:00    0.920850
    2016-07-25 15:00:00    0.201423
    2016-07-25 16:00:00    0.842970
    2016-07-25 17:00:00         NaN
    2016-07-25 21:00:00    1.061207
    2016-07-25 22:00:00    0.232180
    2016-07-25 23:00:00    0.453964
    Name: val, dtype: float64
    

    最后,如果我绘制这个,我仍然会得到不规则的间隙。我需要str 索引,以便matplotlib 不会尝试扩展我的日期。

    s3.reindex(s3.index.strftime('%H:%M'))
    
    timestamp
    00:00    0.740442
    01:00    0.842911
    02:00   -0.873992
    03:00         NaN
    07:00   -0.474993
    08:00   -0.983963
    09:00    0.597011
    10:00   -2.043023
    11:00         NaN
    12:00    0.304668
    13:00    1.185997
    14:00    0.920850
    15:00    0.201423
    16:00    0.842970
    17:00         NaN
    21:00    1.061207
    22:00    0.232180
    23:00    0.453964
    Name: val, dtype: float64
    

    我会将它们绘制在一起,以便我们看到差异。

    f, a = plt.subplots(2, 1, sharey=True, figsize=(10, 5))
    s2.plot(ax=a[0])
    s3.reindex(s3.index.strftime('%H:%M')).plot(ax=a[1])
    

    【讨论】:

    • 这和我想的很接近,谢谢分享!使用resample 代替reindex 似乎是一个好主意,以及ffillnotnull() 的结合使用。想我需要仔细研究如何处理子图以了解最后一块......
    猜你喜欢
    • 2016-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-20
    • 1970-01-01
    • 2021-08-25
    • 2020-08-03
    • 1970-01-01
    相关资源
    最近更新 更多