我正在 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()
其他有关可视化差距的建议也将不胜感激。例如使用某种填充物连接跨越间隙的数据的不同颜色的直线?
答案 0 :(得分: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()
答案 1 :(得分: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()
示例输出:
答案 2 :(得分:0)
您可以遍历 df['col'].isna()
给出的布尔值枚举列表,并将每个布尔值与前一个值进行比较,以选择间隙的 starts
和 stops
的时间戳。以下是基于您的代码示例以及使用 pandas plotting 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()
答案 3 :(得分: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)
之间的填充在 nans 所在的位置创建了我的阴影块。将它们乘以 data.max() 允许它们跨越整个 y 轴。 Step='mid' 方格两边。当数据为 0(不是 NaN)时,Linewidth=0 隐藏红线。