0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

空のDataFrameに対して`pivot_table`を実施すると、`columns`プロパティが空のDataFrameが生成される

Last updated at Posted at 2024-02-15

環境

  • Python 3.12.1
  • pandas 2.2.0

やりたいこと

pandas.pivot_tableで集計したpandas.DataFrameを、CSVファイルに出力したいです。
空のDataFrameを集計した場合は、CSVファイルにはヘッダ行のみ出力したいです。そうすることで、どのようなDataFrameでもCSVファイルの構造(列情報)は保たれるからです。

sample.py
import pandas as pd


def aggregate(df: pd.DataFrame) -> pd.DataFrame:
    return df.pivot_table(index="name", aggfunc="sum", values="working_hours")


df1 = pd.DataFrame(
    {"name": ["A", "A", "B"], "class": ["X", "Y", "Z"], "working_hours": [1, 2, 4]}
)
aggregate(df1).to_csv("df1.csv")

# 空のDataFrameを生成
df2 = pd.DataFrame(columns=["name", "class", "working_hours"])
# df1のdtypesと同じにした
df2 = df2.astype({"working_hours": "int64"})

aggregate(df2).to_csv("df2.csv")

しかし、空のDataFrameを集計した結果をCSVファイルに出力すると、nameしか出力されませんでした。

$ python sample.py

$ cat df1.csv
name,working_hours
A,3
B,4

$ cat df2.csv
name

DataFrameの中身を一つずつ確認する

生成されたDataFrameの中身を一つずつ確認していきます。

空でないDataFrame

集計前のDataFrame

In [208]: df1 = pd.DataFrame(
     ...:     {"name": ["A", "A", "B"], "class": ["X", "Y", "Z"], "working_hours": [1, 2, 4]}
     ...: )

In [209]: df1
Out[209]:
  name class  working_hours
0    A     X              1
1    A     Y              2
2    B     Z              4

In [210]: df1.dtypes
Out[210]:
name             object
class            object
working_hours     int64
dtype: object

集計後のDataFrame

In [218]: df1_agg = df1.pivot_table(index="name", aggfunc="sum", values="working_hours")

In [219]: df1_agg
Out[219]:
      working_hours
name
A                 3
B                 4

In [220]: df1_agg.dtypes
Out[220]:
working_hours    int64
dtype: object

In [221]: df1_agg.columns
Out[221]: Index(['working_hours'], dtype='object')

In [222]: df1_agg.index
Out[222]: Index(['A', 'B'], dtype='object', name='name')

空のDataFrame

集計前のDataFrame

In [229]: df2 = pd.DataFrame(columns=["name", "class", "working_hours"])

In [246]: df2=df2.astype({"working_hours":"float"})

In [247]: df2
Out[247]:
Empty DataFrame
Columns: [name, class, working_hours]
Index: []

In [248]: df2.dtypes
Out[248]:
name              object
class             object
working_hours    float64
dtype: object

集計後のDataFrame

In [249]: df2_agg = df2.pivot_table(index="name", aggfunc="sum", values="working_hours")

In [250]: df2_agg
Out[250]:
Empty DataFrame
Columns: []
Index: []

In [251]: df2_agg.dtypes
Out[251]: Series([], dtype: object)

In [252]: df2_agg.columns
Out[252]: Index([], dtype='object')

In [253]: df2_agg.index
Out[253]: Index([], dtype='object', name='name')

分かったこと

集計後のDataFrameのcolumnsプロパティ

空でないDataFramedf1をpivot_tableで集計すると、columnsプロパティは['working_hours']でした。それに対して、空のDataFramedf2を集計すると、columnsプロパティは[]で空でした。
df1df2columnsプロパティは同じ値なので、pivot_tableで集計した結果のcolumnsプロパティも同じ値になることを、私は期待していました。なぜ同じ値にならないのでしょうか?

直接的な原因は分かりませんでした。
ただ、pivot_tablecolumns="class"引数を指定すると、集計したDataFrameのcolumnsclass列の値['X', 'Y', 'Z']になります。

In [375]: df1_agg1 = df1.pivot_table(index="name", values="working_hours", columns="class", aggfunc="sum",fill_value=0)

In [377]: df1_agg1
Out[377]:
class  X  Y  Z
name
A      1  2  0
B      0  0  4

In [378]: df1_agg1.columns
Out[378]: Index(['X', 'Y', 'Z'], dtype='object', name='class')

pivot_tableで集計したDataFrameのcolumnsは、集計前のDataFrameの値によって変わるので、空のDataFrameをpivot_tableで集計したDataFrameのcolumnsプロパティは空なのかもしれません。

集計後のDataFrameのindexプロパティ

pivot_tableindex引数に渡した列名nameが、indexプロパティのnameに格納されていました。このnameプロパティの値が、CSVファイルdf2.csvに出力されていました。

対応方法

空のDataFrameをpivot_tableで集計しない

aggregate()に渡されたDataFrameが空の場合は、個別に空のDataFrameを返すようにしました。

def aggregate(df: pd.DataFrame) -> pd.DataFrame:
    if len(df) == 0:
        df_empty = pd.DataFrame(columns=["working_hours"], index=pd.Index([],name="name"))
        return df_empty.astype({"working_hours": "int64"})

    return df.pivot_table(index="name", aggfunc="sum", values="working_hours")
$ python sample.py

$ cat df2.csv
name,working_hours
In [242]: df2_agg2 = df2.pivot_table(index="name", aggfunc="sum", values="working_hours", columns="class", fill_value=0)

In [243]: df2_agg2
Out[243]:
Empty DataFrame
Columns: []
Index: []

In [244]: df2_agg2.dtypes
Out[244]: Series([], dtype: object)

In [245]: df2_agg2.columns
Out[245]: Index([], dtype='object', name='class')

pivot_tableでなくgroupbyで集計する

今回の例だとpivot_tablecolumnsプロパティを使用していないので、groupby()でも同じ結果を得ることができました。

def aggregate(df: pd.DataFrame) -> pd.DataFrame:
    return df.groupby("name")[["working_hours"]].sum()
In [380]: df1.groupby("name")[["working_hours"]].sum()
Out[380]:
      working_hours
name
A                 3
B                 4

In [383]: df2_agg3 = df2.groupby("name")[["working_hours"]].sum()

In [384]: df2_agg3.columns
Out[384]: Index(['working_hours'], dtype='object')

補足:空のDataFrameに対してできること

私の知る範囲では、空でないDataFrameに対して実行した結果のcolumnsプロパティは、空のDataFrameに対して実行した結果と同じでした。

以下に、実際に実行した結果を記載します。

merge

In [387]: df3 = pd.DataFrame({"name": ["A", "C", "C"], "country": ["Japan", "U.S.", "U.K."]})

In [388]: df3
Out[388]:
  name country
0    A   Japan
1    C    U.S.
2    C    U.K.

In [393]: df3.merge(df2, on="name", how="left")
Out[393]:
  name country class  working_hours
0    A   Japan   NaN            NaN
1    C    U.S.   NaN            NaN
2    C    U.K.   NaN            NaN

列の追加

# どんな値を設定してもよい
In [398]: df2["category"] = None

In [399]: df2
Out[399]:
Empty DataFrame
Columns: [name, class, working_hours, working_hours*2, category]
Index: []

concat

In [401]: pandas.concat([df1,df2])
Out[401]:
  name class  working_hours
0    A     X              1
1    A     Y              2
2    B     Z              4
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?