如何在pandas中按中位数值对箱线图进行排序?

25

我想画一个以数据框中列Z为横轴,以类别XY为纵轴的箱线图。如何按照中位数的降序排序箱线图?

import pandas as pd
import random
n = 100
# this is probably a strange way to generate random data; please feel free to correct it
df = pd.DataFrame({"X": [random.choice(["A","B","C"]) for i in range(n)], 
                   "Y": [random.choice(["a","b","c"]) for i in range(n)],
                   "Z": [random.gauss(0,1) for i in range(n)]})
df.boxplot(column="Z", by=["X", "Y"])

请注意,这个问题非常相似,但是它们使用了不同的数据结构。我对 pandas 相对较新(只在一般的 Python 教程上做过一些教程),所以我无法弄清楚如何让我的数据与那里发布的答案配合使用。这可能更多的是一个重塑而不是绘图的问题。也许有一种使用 groupby 的解决方案?

4个回答

26

您可以使用 如何在 pandas 中通过中位数值对盒形图进行排序 中的答案,但首先需要分组数据并创建一个新的数据帧:

import pandas as pd
import random
import matplotlib.pyplot as plt

n = 100
# this is probably a strange way to generate random data; please feel free to correct it
df = pd.DataFrame({"X": [random.choice(["A","B","C"]) for i in range(n)], 
                   "Y": [random.choice(["a","b","c"]) for i in range(n)],
                   "Z": [random.gauss(0,1) for i in range(n)]})
grouped = df.groupby(["X", "Y"])

df2 = pd.DataFrame({col:vals['Z'] for col,vals in grouped})

meds = df2.median()
meds.sort_values(ascending=False, inplace=True)
df2 = df2[meds.index]
df2.boxplot()

plt.show()

plot


7
为了使此代码工作(Pandas 0.20.1,Python 3.6.1,Windows 8),我不得不将meds.sort(ascending = False)更改为meds.sort_values(ascending = False,inplace = True) - Stephen McAteer
@StephenMcAteer 感谢您的提示。我没有使用最新版本的Pandas,因此请随意编辑答案并添加您的答案版本以供未来用户使用。 - Alvaro Fuentes
有没有办法在中位数相同时设置备用排序?例如,如果两个中位数相同,则按一个四分位数进行排序。 - rococo

18

类似于Alvaro Fuentes的答案,以函数形式呈现,更具可移植性

import pandas as pd

def boxplot_sorted(df, by, column):
  df2 = pd.DataFrame({col:vals[column] for col, vals in df.groupby(by)})
  meds = df2.median().sort_values()
  df2[meds.index].boxplot(rot=90)

boxplot_sorted(df, by=["X", "Y"], column="Z")

13
为了回答标题中的问题,不涉及绘制两个分类变量所有组合的额外细节:
n = 100
df = pd.DataFrame({"Category": [np.random.choice(["A","B","C","D"]) for i in range(n)],      
                   "Variable": [np.random.normal(0, 10) for i in range(n)]})

grouped = df.loc[:,['Category', 'Variable']] \
    .groupby(['Category']) \
    .median() \
    .sort_values(by='Variable')

sns.boxplot(x=df.Category, y=df.Variable, order=grouped.index)

enter image description here

我添加了这个解决方案,因为将被接受的答案简化为单个变量很困难,我相信人们正在寻找一种方法来做到这一点。我自己多次查看此问题以寻找此类答案。


你的最小示例存在一些不一致之处(在第一个'Category后缺少',在分组和绘图期间从“X”和“Z”声明切换到“Category”和“Variable”)。但是它背后的整体思路对于我基于seaborn的应用程序非常有用。 - Christian Karcher
1
@ChristianKarcher 感谢您指出这些问题。这就是我没有复制和粘贴的后果。 - rocksNwaves

1
我按照被接受的答案操作,但当我想要覆盖使用其他y轴的第二个图时(即ax.twinx()),遇到了一些复杂问题。问题在于第二个图的x轴覆盖了排序顺序。
最终,我只使用了seaborn来完成以下操作。这类似于@rocksNwaves的答案,但我使用了问题中介绍的术语。只需三个步骤:
  1. If you don't mind creating a column that combines "X" and "Y", it will make things easier with seaborn:

    df["XY"] = df["X"] + df["Y"]
    

    Of course, you can combine the two columns in however way you want.

  2. Order by XY and obtain sorted index

    grouped = df.groupby(["XY"])
    order = grouped.median()["Z"].sort_values().index
    
  3. Plot using seaborn

    sns.boxplot(x="XY", y="Z", data=df, order=order)
    

    Note that you can think of order as specifying the order of labels on the x axis.

一个完整的程序:

import pandas as pd
import random
import seaborn as sns
import matplotlib.pyplot as plt
n = 100
# this is probably a strange way to generate random data; please feel free to correct it
df = pd.DataFrame({"X": [random.choice(["A","B","C"]) for i in range(n)],
                   "Y": [random.choice(["a","b","c"]) for i in range(n)],
                   "Z": [random.gauss(0,1) for i in range(n)]})

df["XY"] = df["X"] + df["Y"]
grouped = df.groupby(["XY"])
order = grouped.median()["Z"].sort_values().index
sns.boxplot(x="XY", y="Z", data=df, order=order, palette="light:#5A9")
plt.show()

df looks like

    X  Y         Z
0   A  a  0.894873
1   C  a -0.568682
2   C  b  0.985260
3   B  c  2.056287
...

情节看起来像

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接