Python - 当同时使用 matplotlib 和 pandas 绘图时,x-axis 使用 pandas 是准确的,但不是 matplotlib

Python - When plotting using both matplotlib and pandas, the x-axis is accurate using pandas, but not matplotlib

(这是我第一个 Whosebug 问题!)

我有一个 pandas 数据框,其中包含一天中 15 分钟间隔的太阳辐照度值。此数据帧的索引是“DatetimeIndex”(dtype='datetime64[ns, America/New_York]',并本地化到其各自的时区。因此,索引值以“1997-01-20 00:15:00-05:00”开始,“ 1997-01-20 00:30:00-05:00" ... "1997-01-21 00:00:00-5:00" (注意最后一个条目是第二天凌晨 12 点)。

单独绘制此数据框(名为 poa_irradiance_swd_corrected)时,一切看起来都很棒。这是我的代码及其输出的示例:

代码:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter
import pvlib as pv
import datetime as dt
import math
import pytz
import time

plt.figure()
poa_irradiance_swd_corrected.plot().get_figure().set_facecolor('white')

输出:

此数据框中最高的“poa_global”辐照度测量值介于 12:00 PM(中午)和 1:30-ish 之间。这是准确的。

我还有另外两个数据帧(名为 poa_irradiance_swd_flatmodeled_poa_irradiance_swd)具有类似的辐照度测量值。为了直观地比较辐照度测量值,我使用 matplotlib 的 subplot 函数将所有 3 个数据帧放入一个图中。 然而,X-axis 的子图非常不准确。代码和输出如下所示:

代码:

fig, axs = plt.subplots(2,2, sharex=True,sharey=True, facecolor='white',figsize=(12,8))
fig.suptitle('Sunny Winter Day',size='xx-large',weight='bold')
axs[0,0].plot(poa_irradiance_swd_flat)
axs[0,0].set_title('POA Irradiance (Flat)')
axs[0,1].plot(modeled_poa_irradiance_swd)
axs[0,1].set_title('Modeled Data (Tilted)')
axs[1,0].plot(poa_irradiance_swd_corrected)
axs[1,0].set_title('POA Irradiance (Tilted)')
axs[1,1].plot(modeled_poa_irradiance_swd)
axs[1,1].set_title('Modeled Data (Tilted)')
for axs in axs.flat:
    axs.set(ylabel='Irradiance $W/m^2$')
    axs.xaxis.set_major_formatter(DateFormatter('%H:%M'))
plt.tight_layout(pad=0, w_pad=0, h_pad=3)
fig.autofmt_xdate()

输出: 请注意,右边的两个图完全相同。我只是用右边的图与左边的图进行比较

请注意 x-axis 如何在 6 小时后完全关闭。 我有 cross-checked 数据帧本身,而且,最高辐照度时间与 12:00 下午(中午)和大约 1:30-ish 之间的时间重合...... 不是 在 18:00 附近(这是 6:00 下午,没有意义)。我什至在 axs[1,0].plot(poa_irradiance_swd_corrected) 代码行中使用了完全相同的数据框。

如何让X-axis显示准确的时间?

编辑(回应scespinoza):

所以我尝试从数据框中传递相应的 .index.values 值,并成功绘制...但仍然存在相同的问题。

然后,我尝试将 ax 命令传递给 plot 函数本身,如下所示:每个子图的 poa_irradiance_swd_flat.plot(ax=axs[0, 0])。将 matplotlib 作为 mpl 导入后,我还在代码的顶部添加了 mpl.rcParams['timezone'] = 'UTC'

现在,我收到一个 ValueError:

ValueError                                Traceback (most recent call last)
File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/IPython/core/formatters.py:339, in BaseFormatter.__call__(self, obj)
    337     pass
    338 else:
--> 339     return printer(obj)
    340 # Finally look for special method names
    341 method = get_real_method(obj, self.print_method)

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/IPython/core/pylabtools.py:151, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    148     from matplotlib.backend_bases import FigureCanvasBase
    149     FigureCanvasBase(fig)
--> 151 fig.canvas.print_figure(bytes_io, **kw)
    152 data = bytes_io.getvalue()
    153 if fmt == 'svg':

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/backend_bases.py:2295, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2289     renderer = _get_renderer(
   2290         self.figure,
   2291         functools.partial(
   2292             print_method, orientation=orientation)
   2293     )
   2294     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2295         self.figure.draw(renderer)
   2297 if bbox_inches:
   2298     if bbox_inches == "tight":

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/artist.py:73, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     71 @wraps(draw)
     72 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 73     result = draw(artist, renderer, *args, **kwargs)
     74     if renderer._rasterizing:
     75         renderer.stop_rasterizing()

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/artist.py:50, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     47     if artist.get_agg_filter() is not None:
     48         renderer.start_filter()
---> 50     return draw(artist, renderer)
     51 finally:
     52     if artist.get_agg_filter() is not None:

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/figure.py:2810, in Figure.draw(self, renderer)
   2807         # ValueError can occur when resizing a window.
   2809 self.patch.draw(renderer)
-> 2810 mimage._draw_list_compositing_images(
   2811     renderer, self, artists, self.suppressComposite)
   2813 for sfig in self.subfigs:
   2814     sfig.draw(renderer)

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/image.py:132, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    130 if not_composite or not has_images:
    131     for a in artists:
--> 132         a.draw(renderer)
    133 else:
    134     # Composite any adjacent images together
    135     image_group = []

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/artist.py:50, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     47     if artist.get_agg_filter() is not None:
     48         renderer.start_filter()
---> 50     return draw(artist, renderer)
     51 finally:
     52     if artist.get_agg_filter() is not None:

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/axes/_base.py:3046, in _AxesBase.draw(self, renderer)
   3043     for spine in self.spines.values():
   3044         artists.remove(spine)
-> 3046 self._update_title_position(renderer)
   3048 if not self.axison:
   3049     for _axis in self._get_axis_list():

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/axes/_base.py:2986, in _AxesBase._update_title_position(self, renderer)
   2983 for ax in axs:
   2984     if (ax.xaxis.get_ticks_position() in ['top', 'unknown']
   2985             or ax.xaxis.get_label_position() == 'top'):
-> 2986         bb = ax.xaxis.get_tightbbox(renderer)
   2987     else:
   2988         bb = ax.get_window_extent(renderer)

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/axis.py:1103, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1100 if not self.get_visible():
   1101     return
-> 1103 ticks_to_draw = self._update_ticks()
   1105 self._update_label_position(renderer)
   1107 # go back to just this axis's tick labels

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/axis.py:1046, in Axis._update_ticks(self)
   1041 """
   1042 Update ticks (position and labels) using the current data interval of
   1043 the axes.  Return the list of ticks that will be drawn.
   1044 """
   1045 major_locs = self.get_majorticklocs()
-> 1046 major_labels = self.major.formatter.format_ticks(major_locs)
   1047 major_ticks = self.get_major_ticks(len(major_locs))
   1048 self.major.formatter.set_locs(major_locs)

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/ticker.py:224, in Formatter.format_ticks(self, values)
    222 """Return the tick labels for all the ticks at once."""
    223 self.set_locs(values)
--> 224 return [self(value, i) for i, value in enumerate(values)]

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/ticker.py:224, in <listcomp>(.0)
    222 """Return the tick labels for all the ticks at once."""
    223 self.set_locs(values)
--> 224 return [self(value, i) for i, value in enumerate(values)]

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/dates.py:636, in DateFormatter.__call__(self, x, pos)
    635 def __call__(self, x, pos=0):
--> 636     result = num2date(x, self.tz).strftime(self.fmt)
    637     return _wrap_in_tex(result) if self._usetex else result

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/dates.py:528, in num2date(x, tz)
    526 if tz is None:
    527     tz = _get_rc_timezone()
--> 528 return _from_ordinalf_np_vectorized(x, tz).tolist()

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/numpy/lib/function_base.py:2163, in vectorize.__call__(self, *args, **kwargs)
   2160     vargs = [args[_i] for _i in inds]
   2161     vargs.extend([kwargs[_n] for _n in names])
-> 2163 return self._vectorize_call(func=func, args=vargs)

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/numpy/lib/function_base.py:2246, in vectorize._vectorize_call(self, func, args)
   2243 # Convert args to object arrays first
   2244 inputs = [asanyarray(a, dtype=object) for a in args]
-> 2246 outputs = ufunc(*inputs)
   2248 if ufunc.nout == 1:
   2249     res = asanyarray(outputs, dtype=otypes[0])

File ~/opt/miniconda3/envs/pvlib/lib/python3.10/site-packages/matplotlib/dates.py:350, in _from_ordinalf(x, tz)
    347 dt = (np.datetime64(get_epoch()) +
    348       np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us'))
    349 if dt < np.datetime64('0001-01-01') or dt >= np.datetime64('10000-01-01'):
--> 350     raise ValueError(f'Date ordinal {x} converts to {dt} (using '
    351                      f'epoch {get_epoch()}), but Matplotlib dates must be '
    352                       'between year 0001 and 9999.')
    353 # convert from datetime64 to datetime:
    354 dt = dt.tolist()

ValueError: Date ordinal 14228655 converts to 40926-09-26T00:00:00.000000 (using epoch 1970-01-01T00:00:00), but Matplotlib dates must be between year 0001 and 9999.

无法真正重现您的问题,但也许您可以尝试将系列的索引和值分别传递给 plot 函数。

...
axs[0,0].plot(poa_irradiance_swd_flat.index, poa_irradiance_swd_flat.values)
...

此外,请注意,您可以将 ax 属性传递给 Series.plot() 函数,因此您可以将代码重构为如下内容:

fig, axs = plt.subplots(2,2, sharex=True,sharey=True, facecolor='white',figsize=(12,8))
fig.suptitle('Sunny Winter Day',size='xx-large',weight='bold')
poa_irradiance_swd_flat.plot(ax=axs[0, 0])
axs[0,0].set_title('POA Irradiance (Flat)')
modeled_poa_irradiance_swd.plot(ax=axs[0, 1])
axs[0,1].set_title('Modeled Data (Tilted)')
poa_irradiance_swd_corrected.plot(ax=axs[1, 0])
axs[1,0].set_title('POA Irradiance (Tilted)')
modeled_poa_irradiance_swd.plot(ax=axs[1, 1])
axs[1,1].set_title('Modeled Data (Tilted)')
for axs in axs.flat:
    axs.set(ylabel='Irradiance $W/m^2$')
    # axs.xaxis.set_major_formatter(DateFormatter('%H:%M'))
plt.tight_layout(pad=0, w_pad=0, h_pad=3)
fig.autofmt_xdate()

更新

我认为错误可能来自 DateFormatter 转换日期值的时区。尝试将其添加到程序的顶部。

import matplotlib as mpl

mpl.rcParams['timezone'] = 'UTC'