繁体   English   中英

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

[英]Highlight data gaps (NaN) in Matplotlib Scatter Plot

我正在绘制来自 matplotlib 中 pandas 的一些基于时间的数据(可以是数万行),我想突出显示数据中存在 NaN 的时段。 我认为实现这一点的方法是使用 axvspan 在 plot 上绘制一个红色框,在有数据间隙的地方开始和停止。 我确实考虑过每次使用 axvline 有一个 NaN 时只画一条垂直线,但这可能会在 plot 上创建数千个对象,并导致生成的 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()

结果看起来像下面的模型: 在此处输入图像描述

其他可视化间隙的建议也将不胜感激。 比如一条不同颜色的直线使用某种填充连接跨越间隙的数据?

要查找 NaN 组的开始和停止索引,您可以首先创建一个变量来保存 boolean 值,其中colNaN 使用此变量,您可以找到validNaN值之间存在转换的行。 这可以使用shift (在数据帧上移动一行)和ne来完成,这样您可以比较两个连续的行并确定值交替的位置。 之后,应用cumsum创建不同组的validNaN值的连续数据。

现在,仅使用具有NaN值的行 ( df[is_nan] ) 使用groupbyn_groups来收集同一组内的间隙。 接下来,应用aggregate以返回单个元组,其中包含每个组的开始和结束时间戳。 这里使用DateOffset是为了将矩形显示扩展到所需图像 output 之后的相邻点。 您现在可以使用['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

我们可以使用fill_between来突出显示区域。 但是,定义数据所在的部分比定义没有数据的部分要容易得多,而不会与现有数据点产生差距。 因此,我们只需突出显示整个绘图区域,然后覆盖数据为白色的区域,然后是 plot:

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()

样品 output:

在此处输入图像描述

您可以遍历由df['col'].isna()给出的 boolean 值的枚举列表,并将每个 boolean 值与前一个值进行比较,以stops的时间戳starts 这是一个基于您的代码示例的示例,其中 plot 是使用pandas 绘制 function生成的:

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()

时间间隔

最后,我从提供的答案中从 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)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM