突出显示散点图中的数据间隙 (NaN)

问题描述 投票:0回答:4

我正在 matplotlib 中绘制来自 pandas 的一些基于时间的数据(可以是数万行),我想突出显示数据中存在 NaN 的时期。 我实现此目的的方法是使用 axvspan 在绘图上有数据间隙的地方开始和停止处绘制一个红色框。 我确实考虑过每次使用 axvline 出现 NaN 时绘制一条垂直线,但这可能会在绘图上创建数千个对象,并导致生成的 PNG 需要很长时间才能写入。所以我认为使用axvspan是比较合适的。然而,我陷入困境的是找到 NaN 组的开始和停止索引。

下面的代码不是来自我的实际代码,只是一个基本的模型来展示我想要实现的目标。

import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import matplotlib.pyplot as plt

days = pd.date_range(datetime.now(), datetime.now() + timedelta(13), freq='D')
data = [2,2.3,3,np.nan, np.nan,4.7,3.4,3.1,2.7,np.nan,np.nan,np.nan,4,4.5]
df = pd.DataFrame({'idx': days, 'col': data})
df = df.set_index('idx')
print(df)

#Code to find the start index and stop index of the groups of NaNs
# resuls in list which contains lists of each gap start and stop datetime
gaps = []

plt.plot(df.index, df['col'])

for gap in gaps: 
    plt.axvspan(gap[0], gap[1], facecolor='r', alpha=0.5)

plt.show()

结果将类似于下面的模型: enter image description here

其他可视化差距的建议也将受到赞赏。例如使用某种填充物将数据连接到间隙上的不同颜色的直线?

python python-3.x pandas numpy matplotlib
4个回答
1
投票

要查找 NaN 组的起始和终止索引,您可以首先创建一个变量来保存布尔值,其中

col
NaN
。使用此变量,您可以找到
valid
NaN
值之间存在过渡的行。这可以使用
shift
(使数据帧上的一行错位)和
ne
来完成,这样您就可以比较两个连续行并确定值交替的位置。之后,应用
cumsum
创建不同的连续数据组
valid
NaN
值。

现在,仅使用具有

NaN
值 (
df[is_nan]
) 的行,使用
groupby
n_groups
来收集同一组内的间隙。接下来,应用
aggregate
返回包含每个组的开始和结束时间戳的单个元组。这里使用
DateOffset
是将矩形显示扩展到所需图像输出之后的相邻点。您现在可以使用
['col'].values
访问
aggregate
返回的数据帧并将其转换为列表。

...
...
df = df.set_index('idx')
print(df)

# Code to find the start index and stop index of the groups of NaNs
is_nan = df['col'].isna()
n_groups = is_nan.ne(is_nan.shift()).cumsum()
gap_list = df[is_nan].groupby(n_groups).aggregate(
    lambda x: (
        x.index[0] + pd.DateOffset(days=-1),
        x.index[-1] + pd.DateOffset(days=+1)
    )
)["col"].values

# resuls in list which contains tuples of each gap start and stop datetime
gaps = gap_list

plt.plot(df.index, df['col'], marker='o' )
plt.xticks(df.index, rotation=45)

for gap in gaps:
    plt.axvspan(gap[0], gap[1], facecolor='r', alpha=0.5)

plt.grid()
plt.show()

plot_nan_gaps


1
投票

我们可以使用

fill_between
来突出显示区域。然而,定义有数据的部分比没有数据的部分要容易得多,而且不会与现有数据点产生间隙。因此,我们只需突出显示整个绘图区域,然后覆盖数据为白色的区域,然后绘制:

import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import matplotlib.pyplot as plt


days = pd.date_range(datetime.now(), datetime.now() + timedelta(13), freq='D')
data = [2,2.3,3,np.nan, np.nan,4.7,3.4,3.1,2.7,np.nan,np.nan,np.nan,4,4.5]
df = pd.DataFrame({'idx': days, 'col': data})
df = df.set_index('idx')


fig, ax = plt.subplots()
ax.fill_between(df.index, df.col.min(), df.col.max(), where=df.col, facecolor="lightblue", alpha=0.5)
ax.fill_between(df.index, df.col.min(), df.col.max(), where=np.isfinite(df.col), facecolor="white", alpha=1)
ax.plot(df.index, df.col)

ax.xaxis.set_tick_params(rotation=45)
plt.tight_layout()
plt.show()

输出示例:

enter image description here


0
投票

您可以循环遍历

df['col'].isna()
给出的布尔值枚举列表,并将每个布尔值与前一个布尔值进行比较,以选择间隙的
starts
stops
的时间戳。这是一个基于您的代码示例的示例,其中绘图是使用 pandas 绘图函数生成的:

import numpy as np               # v 1.19.2
import pandas as pd              # v 1.2.3
import matplotlib.pyplot as plt  # v 3.3.4

days = pd.date_range('2021-03-08', periods=14, freq='D')
data = [2,2.3,3,np.nan, np.nan,4.7,3.4,3.1,2.7,np.nan,np.nan,np.nan,4,4.5]
df = pd.DataFrame(dict(col=data), index=days)

ax = df.plot(y='col', marker='.', figsize=(8,4))

# Generate lists of starts and stops timestamps for gaps in time series,
# assuming that the first and last data points are not NaNs
starts, stops = [], []
for idx, isna in enumerate(df['col'].isna()):
    if isna != df['col'].isna()[idx-1] and isna:
        starts.append(df.index[idx-1])
    elif isna != df['col'].isna()[idx-1] and not isna:
        stops.append(df.index[idx])

# Plot red vertical spans for gaps in time series
for start, stop in zip(starts, stops): 
    ax.axvspan(start, stop, facecolor='r', alpha=0.3)

plt.show()

time_gaps


0
投票

最后我从A、B、C栏提供的答案中摘取了一点,感谢您的反馈。 对于现实世界的数据(数十万行)来说,构建起止点列表非常慢。因为我不需要数字答案,只需要一个视觉答案,所以我单独使用 matplotlib 并使用以下代码完成了它:

ax[i].fill_between(data.index, 0, (is_nan*data.max()), color='r', step='mid', linewidth='0')
ax[i].plot(data.index, data, color='b', linestyle='-', marker=',', label=ylabel)

之间的填充在 nan 所在的位置创建了我的阴影块。将它们乘以 data.max() 可以让它们跨越整个 y 轴。 Step='mid' 使两侧呈正方形。 Linewidth=0 当数据为 0(不是 NaN)时隐藏红线。

© www.soinside.com 2019 - 2024. All rights reserved.