Yearly BoxPlots with Pandas

2019-07-23 05:51发布

I have a DataFrame (multiple daily timeseries) with DateTimeIndex as index and MultiIndex as columns. I would like to select a column and create a Box Plot where data are grouped by year. I thought it was easy but I am struggling to get some result.

>>> daily.shape
(11319, 118)

>>> daily.index
DatetimeIndex(['1986-01-01', '1986-01-02', '1986-01-03', '1986-01-04',
               '1986-01-05', '1986-01-06', '1986-01-07', '1986-01-08',
               '1986-01-09', '1986-01-10',
               ...
               '2016-12-22', '2016-12-23', '2016-12-24', '2016-12-25',
               '2016-12-26', '2016-12-27', '2016-12-28', '2016-12-29',
               '2016-12-30', '2016-12-31'],
              dtype='datetime64[ns]', name='timevalue', length=11319, freq=None)
>>> daily.columns
MultiIndex(levels=[['41B001', '41B004', '41B006', '41B008', '41B011', '41MEU1', '41N043', '41R001', '41R002', '41R012', '41WOL1', '41WOL2', '47E013', 'T1M001', 'T1M003'], ['BA-10.0', 'BA-2.5', 'BC', 'CO', 'CO2', 'NO', 'NO2', 'NOx', 'O3', 'PM-10.0', 'PM-2.5', 'RH', 'SO2', 'T', 'UVPM', 'VO-10.0', 'VO-2.5', 'WD', 'WS-s', 'WS-v', 'p']],
           labels=[[0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14], [5, 6, 7, 3, 5, 6, 7, 8, 3, 5, 6, 7, 8, 3, 5, 6, 7, 12, 0, 1, 5, 6, 7, 8, 9, 10, 15, 16, 0, 1, 5, 6, 7, 9, 10, 15, 16, 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 2, 3, 4, 5, 6, 7, 12, 14, 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 0, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 4, 5, 6, 7, 12, 11, 13, 13, 17, 18, 19, 20, 11, 13, 13, 17, 18, 19, 20]],
           names=['sitekey', 'measurandkey'])

The best I could achieve is:

fig, axe = plt.subplots()
daily.loc[:,[('41R001', 'SO2')]].groupby(daily.index.map(lambda x: x.year)).boxplot(ax=axe, subplots=False, rot=90)

But It will requires other postprocess for labelling axis.

When I try to reset_index() to apply function and using pivot(), I have indexing error because of the MultiIndex.

d = daily.reset_index()
d['timevalue']

The Exception is: cannot handle a non-unique multi-index! That I do not understand since there is no occurrence of TimeValue in my MultiIndex. I also have tried .loc[] but I think the problem is elsewhere.

So, what I would achieve is simple:

  • I have daily timeseries among years and those timeseries are multi-indexed;
  • I would like to select one of them (using loc and a composite key as in example above) and get a timeserie boxplot where data are grouped by year.

I thought it could be easy, but I cannot properly use pivot() with this DataFrame because of the mutli-index error.

2条回答
再贱就再见
2楼-- · 2019-07-23 06:54

You were on the right track using groupby and pivot. First, let's create some dummy data:

# create index
index = pd.DatetimeIndex(pd.date_range("1986-01-01", periods=200, freq="w"))

# create columns
col_lvl_1 = ['41B001', '41B004', '41B006']
col_lvl_2 = ['BA-10.0', 'BA-2.5', 'BC']
columns = pd.MultiIndex.from_product([col_lvl_1, col_lvl_2], names=["Lvl1", "Lvl2"])

# random data
data = np.random.randint(0, 100, size=(200, 9))

# create df
df = pd.DataFrame(data, index=index, columns=columns)
df["year"] = df.index.year

print(df.head())

Lvl1        41B001                  41B004                  41B006                  year
Lvl2        BA-10.0     BA-2.5  BC  BA-10.0     BA-2.5  BC  BA-10.0     BA-2.5  BC  
1986-01-05  81          41      52  87          73      41  14          20      66  1986
1986-01-12  14          27      33  96          69      85  93          28      45  1986
1986-01-19  31          46      87  88          19      62  89          50      1   1986
1986-01-26  21          6       45  2           73      64  71          42      38  1986
1986-02-02  76          94      33  64          33      56  91          43      42  1986

Now, you can

  • iterate through your column values (except the year column),
  • subset the data frame with the current column including year,
  • pivot your sub data frame to have years as columns
  • and finally plot it.

That's it.

for column in df.columns.values[:-1]:
    sub_df = df.loc[:, [column, ("year", "")]]
    pivot_df = sub_df.pivot(columns="year")
    pivot_df.columns = pivot_df.columns.levels[2]
    pivot_df.plot(kind="box", title=column)

enter image description here enter image description here

And more pictures following here...

查看更多
对你真心纯属浪费
3楼-- · 2019-07-23 06:56

If you don't mind using seaborn library you can make this plot pretty easily:

import pandas as pd
import seaborn as sns

index = pd.DatetimeIndex(start=pd.to_datetime('1985-01-01'), 
                         end = pd.to_datetime('2017-03-08'), 
                         freq='d')
df = pd.DataFrame(index = index, 
                  data = np.random.uniform(-1,1,size=(index.shape[0],4)), 
                  columns=pd.MultiIndex.from_arrays([['A','A','B','B'],
                                                     ['d','e','d','e']]))
df['Year'] = df.index.year
#                    A                   B            Year
#                    d         e         d         e      
# 1985-01-01  0.205208 -0.228484  0.296273  0.545031  1985
# 1985-01-02  0.546436 -0.538920  0.173388  0.848590  1985
# 1985-01-03 -0.367593 -0.974911 -0.796331 -0.946239  1985
# 1985-01-04 -0.346102 -0.951542 -0.975172  0.951099  1985
# 1985-01-05  0.973975  0.708254 -0.150454  0.145298  1985

ax = sns.boxplot(data = df, x='Year',y=('A','e'))
for item in ax.get_xticklabels():
    item.set_rotation(90)

The resulting image:

yearly boxplot

I tried using the pandas.DataFrame.boxplot() method but couldn't make it work for this case in a short span of time =).

查看更多
登录 后发表回答