如何融化 pandas 数据框?

How do I melt a pandas dataframe?

标签上,我经常看到用户在pandas中询问有关熔化数据框的问题。我将尝试对这个主题进行规范的问答(自我回答)。

我要澄清一下:

  1. 什么是融化?

  2. 如何使用 melt?

  3. 我什么时候使用融化?

我看到一些关于 melt 的更热门的问题,比如:

因此,我将尝试针对该主题进行规范的问答。



数据集:

我将在这个数据集上得到我所有的答案,这些数据集是随机年龄的随机人的随机成绩(更容易解释答案 :D):

import pandas as pd
df = pd.DataFrame({'Name': ['Bob', 'John', 'Foo', 'Bar', 'Alex', 'Tom'], 
                   'Math': ['A+', 'B', 'A', 'F', 'D', 'C'], 
                   'English': ['C', 'B', 'B', 'A+', 'F', 'A'],
                   'Age': [13, 16, 16, 15, 15, 13]})


>>> df
   Name Math English  Age
0   Bob   A+       C   13
1  John    B       B   16
2   Foo    A       B   16
3   Bar    F      A+   15
4  Alex    D       F   15
5   Tom    C       A   13
>>> 

问题:

我会遇到一些问题,这些问题将在我下面的自我回答中得到解决。

问题 1:

如何融化数据框,使原始数据框变成:

    Name  Age  Subject Grade
0    Bob   13  English     C
1   John   16  English     B
2    Foo   16  English     B
3    Bar   15  English    A+
4   Alex   17  English     F
5    Tom   12  English     A
6    Bob   13     Math    A+
7   John   16     Math     B
8    Foo   16     Math     A
9    Bar   15     Math     F
10  Alex   17     Math     D
11   Tom   12     Math     C

我想转置它,这样一列就是每个科目,另一列就是学生的重复姓名以及年龄和分数。

问题 2:

这个和问题1类似,但是这次我想让问题1输出的Subject列只有Math,我想过滤掉English列:

   Name  Age Subject Grades
0   Bob   13    Math     A+
1  John   16    Math      B
2   Foo   16    Math      A
3   Bar   15    Math      F
4  Alex   15    Math      D
5   Tom   13    Math      C

我希望输出像上面那样。

问题 3:

如果我要对熔体进行分组并根据分数对学生进行排序,我将如何做到这一点,以获得如下所示的所需输出:

  value             Name                Subjects
0     A         Foo, Tom           Math, English
1    A+         Bob, Bar           Math, English
2     B  John, John, Foo  Math, English, English
3     C         Tom, Bob           Math, English
4     D             Alex                    Math
5     F        Bar, Alex           Math, English

我需要对它进行排序,名称以逗号分隔,Subjects 以相同的顺序分别以逗号分隔

问题 4:

我如何 unmelt 融化的数据框?假设我已经融化了这个数据框:

print(df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades'))

成为:

    Name  Age  Subject Grades
0    Bob   13     Math     A+
1   John   16     Math      B
2    Foo   16     Math      A
3    Bar   15     Math      F
4   Alex   15     Math      D
5    Tom   13     Math      C
6    Bob   13  English      C
7   John   16  English      B
8    Foo   16  English      B
9    Bar   15  English     A+
10  Alex   15  English      F
11   Tom   13  English      A

那么我如何将其转换回原始数据框,如下所示:

   Name Math English  Age
0   Bob   A+       C   13
1  John    B       B   16
2   Foo    A       B   16
3   Bar    F      A+   15
4  Alex    D       F   15
5   Tom    C       A   13

我该怎么做?

问题 5:

如果我要按学生姓名分组,并用逗号分隔科目和年级,我该怎么做?

   Name        Subject Grades
0  Alex  Math, English   D, F
1   Bar  Math, English  F, A+
2   Bob  Math, English  A+, C
3   Foo  Math, English   A, B
4  John  Math, English   B, B
5   Tom  Math, English   C, A

我想要一个像上面那样的数据框。

问题 6:

如果我要完全融化我的数据框,所有列都作为值,我该怎么做?

     Column Value
0      Name   Bob
1      Name  John
2      Name   Foo
3      Name   Bar
4      Name  Alex
5      Name   Tom
6      Math    A+
7      Math     B
8      Math     A
9      Math     F
10     Math     D
11     Math     C
12  English     C
13  English     B
14  English     B
15  English    A+
16  English     F
17  English     A
18      Age    13
19      Age    16
20      Age    16
21      Age    15
22      Age    15
23      Age    13

我想要一个像上面那样的数据框。所有列作为值。

请检查下面我的自我回答:)

请注意 pandas 版本低于 0.20.0 的用户,我将使用 df.melt(...) 作为示例,但您的版本对于 df.melt 来说太低了,您会需要使用 pd.melt(df, ...) 代替。

文档参考:

此处的大多数解决方案都将与 melt, so to know the method melt, see the documentaion 说明一起使用

Unpivot a DataFrame from wide to long format, optionally leaving identifiers set.

This function is useful to massage a DataFrame into a format where one or more columns are identifier variables (id_vars), while all other columns, considered measured variables (value_vars), are “unpivoted” to the row axis, leaving just two non-identifier columns, ‘variable’ and ‘value’.

参数为:

Parameters

  • id_vars : tuple, list, or ndarray, optional

    Column(s) to use as identifier variables.

  • value_vars : tuple, list, or ndarray, optional

    Column(s) to unpivot. If not specified, uses all columns that are not set as id_vars.

  • var_name : scalar

    Name to use for the ‘variable’ column. If None it uses frame.columns.name or ‘variable’.

  • value_name : scalar, default ‘value’

    Name to use for the ‘value’ column.

  • col_level : int or str, optional

    If columns are a MultiIndex then use this level to melt.

  • ignore_index : bool, default True

    If True, original index is ignored. If False, the original index is retained. Index labels will be repeated as necessary.

    New in version 1.1.0.

融化逻辑:

Melting合并多列,将dataframe由宽变长,问题1的解决方法(见下文),步骤为:

  1. 首先我们得到了原始数据框。

  2. 然后 melt 首先合并 MathEnglish 列并复制数据帧(更长)。

  3. 最后添加Subject列,分别作为Grades列值的主题。

这是 melt 函数的简单逻辑。

解决方案:

我会解决我自己的问题。

问题 1:

问题 1 可以使用 pd.DataFrame.melt 和以下代码解决:

print(df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades'))

此代码将 id_vars 参数传递给 ['Name', 'Age'],然后自动将 value_vars 设置为其他列 (['Math', 'English']),它被转置为格式。

您也可以使用 stack 解决问题 1,如下所示:

print(
    df.set_index(["Name", "Age"])
    .stack()
    .reset_index(name="Grade")
    .rename(columns={"level_2": "Subject"})
    .sort_values("Subject")
    .reset_index(drop=True)
)

此代码将 NameAge 列设置为索引并将其余列 MathEnglish 堆叠起来,并重置索引并分配 Grade 作为列名,然后将另一列 level_2 重命名为 Subject 然后按 Subject 列排序,最后再次重置索引。

这两个解决方案输出:

    Name  Age  Subject Grade
0    Bob   13  English     C
1   John   16  English     B
2    Foo   16  English     B
3    Bar   15  English    A+
4   Alex   17  English     F
5    Tom   12  English     A
6    Bob   13     Math    A+
7   John   16     Math     B
8    Foo   16     Math     A
9    Bar   15     Math     F
10  Alex   17     Math     D
11   Tom   12     Math     C

问题 2:

这和我的第一个问题类似,但是这个我只在Math列中过滤,这次value_vars参数就可以使用了,如下:

print(
    df.melt(
        id_vars=["Name", "Age"],
        value_vars="Math",
        var_name="Subject",
        value_name="Grades",
    )
)

或者我们也可以使用 stack 和列规范:

print(
    df.set_index(["Name", "Age"])[["Math"]]
    .stack()
    .reset_index(name="Grade")
    .rename(columns={"level_2": "Subject"})
    .sort_values("Subject")
    .reset_index(drop=True)
)

这两种解决方案都给出:

   Name  Age Subject Grade
0   Bob   13    Math    A+
1  John   16    Math     B
2   Foo   16    Math     A
3   Bar   15    Math     F
4  Alex   15    Math     D
5   Tom   13    Math     C

问题 3:

问题 3 可以用 melt and groupby 解决,使用 agg 函数和 ', '.join,如下所示:

print(
    df.melt(id_vars=["Name", "Age"])
    .groupby("value", as_index=False)
    .agg(", ".join)
)

它融化了数据框,然后按等级分组并聚合它们并用逗号连接它们。

stack也可以用来解决这个问题,stackgroupby如下:

print(
    df.set_index(["Name", "Age"])
    .stack()
    .reset_index()
    .rename(columns={"level_2": "Subjects", 0: "Grade"})
    .groupby("Grade", as_index=False)
    .agg(", ".join)
)

stack 函数只是以等同于 melt 的方式转置数据框,然后重置索引,重命名列、组和聚合。

两种解决方案输出:

  Grade             Name                Subjects
0     A         Foo, Tom           Math, English
1    A+         Bob, Bar           Math, English
2     B  John, John, Foo  Math, English, English
3     C         Bob, Tom           English, Math
4     D             Alex                    Math
5     F        Bar, Alex           Math, English

问题 4:

我们首先融化输入数据的数据帧:

df = df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades')


那么现在我们可以开始解决这个问题4了。

问题 4 可以用 pivot_table, we would have to specify to the pivot_table 个参数解决,valuesindexcolumnsaggfunc.

我们可以用下面的代码解决:

print(
    df.pivot_table("Grades", ["Name", "Age"], "Subject", aggfunc="first")
    .reset_index()
    .rename_axis(columns=None)
)

输出:

   Name  Age English Math
0  Alex   15       F    D
1   Bar   15      A+    F
2   Bob   13       C   A+
3   Foo   16       B    A
4  John   16       B    B
5   Tom   13       A    C

融化的数据帧被转换回与原始数据帧完全相同的格式。

我们首先旋转融化的数据框,然后重置索引并删除列轴名称。

问题 5:

问题 5 可以用 melt and groupby 解决,如下所示:

print(
    df.melt(id_vars=["Name", "Age"], var_name="Subject", value_name="Grades")
    .groupby("Name", as_index=False)
    .agg(", ".join)
)

Name 融化和分组。

或者你可以 stack:

print(
    df.set_index(["Name", "Age"])
    .stack()
    .reset_index()
    .groupby("Name", as_index=False)
    .agg(", ".join)
    .rename({"level_2": "Subjects", 0: "Grades"}, axis=1)
)

两个代码输出:

   Name       Subjects Grades
0  Alex  Math, English   D, F
1   Bar  Math, English  F, A+
2   Bob  Math, English  A+, C
3   Foo  Math, English   A, B
4  John  Math, English   B, B
5   Tom  Math, English   C, A

问题 6:

问题 6 可以用 melt 解决,不需要指定列,只需指定预期的列名:

print(df.melt(var_name='Column', value_name='Value'))

这会融化整个数据框

或者你可以 stack:

print(
    df.stack()
    .reset_index(level=1)
    .sort_values("level_1")
    .reset_index(drop=True)
    .set_axis(["Column", "Value"], axis=1)
)

两个代码输出:

     Column Value
0       Age    16
1       Age    15
2       Age    15
3       Age    16
4       Age    13
5       Age    13
6   English    A+
7   English     B
8   English     B
9   English     A
10  English     F
11  English     C
12     Math     C
13     Math    A+
14     Math     D
15     Math     B
16     Math     F
17     Math     A
18     Name  Alex
19     Name   Bar
20     Name   Tom
21     Name   Foo
22     Name  John
23     Name   Bob

结论:

melt是一个非常好用的功能,经常需要用到,一旦你遇到这类问题,别忘了试试melt,它可能会很好地解决你的问题。

请记住,对于 pandas 版本低于 0.20.0 的用户,您必须使用 pd.melt(df, ...) 而不是 df.melt(...)


问题中未提及的另一种melt是,对于其列header包含公共后缀的数据框,您希望将后缀融化为列值。

这与How can I pivot a dataframe?

中的问题11有点相反

假设您有一个以下 DataFrame,并且您想要将 19701980 融化为列值

  A1970 A1980  B1970  B1980         X  id
0     a     d    2.5    3.2 -1.085631   0
1     b     e    1.2    1.3  0.997345   1
2     c     f    0.7    0.1  0.282978   2

这种情况你可以试试pandas.wide_to_long

pd.wide_to_long(df, stubnames=["A", "B"], i="id", j="year")
                X  A    B
id year
0  1970 -1.085631  a  2.5
1  1970  0.997345  b  1.2
2  1970  0.282978  c  0.7
0  1980 -1.085631  d  3.2
1  1980  0.997345  e  1.3
2  1980  0.282978  f  0.1