pandas

【Pandas】複数行ヘッダーのデータを読み込む

MAX

ExcelやCSVなどで複数行ヘッダーのデータを読み込む際は、列をマルチインデックスとして扱う必要がある。

元データがエクセルの場合、セル結合の有無で読み込まれ方が異なる。

また、マルチインデックスを解除する方法についても紹介する。(アンダースコアで繋げるだけ)

個人的には、ヘッダーが複数行の場合、セルは結合されている方がやりやすい気がする。

スポンサーリンク

ヘッダーが複数行データの基本的な読み込み

pandasのread_csvやread_excel引数でheaderを指定できるため、複数行のヘッダーを指定できる。

1# Excelファイルの1行目と2行目をヘッダーとして読み込む
2df = pd.read_excel("../data/df_multi_columns.xlsx", header=[0,1])
3print(df)
4#    A           C           B                        
5#   AA  CC  BB  AA  CC  BB  AA  CC CC.1 AA.1 AA.2 AA.3
6# 0  1   6  11  21  26  31  41  46   51   61   66   71
7# 1  2   7  12  22  27  32  42  47   52   62   67   72
8# 2  3   8  13  23  28  33  43  48   53   63   68   73
9# 3  4   9  14  23  29  34  44  49   54   64   69   74
10# 4  5  10  15  25  30  35  45  50   55   65   70   75

セル結合がある場合とない場合で、列名が変わってくる。

セル結合のあるデータの読み込み

以下のようなデータを読み込む。

セル結合があり、空白のセルも含まれるデータ。

1df1 = pd.read_excel("../data/df_multi_columns.xlsx", header=[0,1])
2print(df1)
3#    A           C           B                        
4#   AA  CC  BB  AA  CC  BB  AA  CC CC.1 AA.1 AA.2 AA.3
5# 0  1   6  11  21  26  31  41  46   51   61   66   71
6# 1  2   7  12  22  27  32  42  47   52   62   67   72
7# 2  3   8  13  23  28  33  43  48   53   63   68   73
8# 3  4   9  14  23  29  34  44  49   54   64   69   74
9# 4  5  10  15  25  30  35  45  50   55   65   70   75

ヘッダー1行目は空白は空白のまま扱われる。

ヘッダー2行目は空白の場合、「左側の列名.N」のような形で列名が作成される。

1行目と2行目を合わせるとユニークになるように設定される。

列をMultiIndexとして取得

各列は以下のように設定される。

1df1.columns
2# MultiIndex([('A',   'AA'),
3#             ('A',   'CC'),
4#             ('A',   'BB'),
5#             ('C',   'AA'),
6#             ('C',   'CC'),
7#             ('C',   'BB'),
8#             ('B',   'AA'),
9#             ('B',   'CC'),
10#             ('B', 'CC.1'),
11#             ('B', 'AA.1'),
12#             ('B', 'AA.2'),
13#             ('B', 'AA.3')],
14#            )
15
16df1.columns[0]
17# ('A', 'AA')
18
19df1.columns[0][1]
20# 'AA'

タプルのリストが返ってくる。

レベル別に列名を取得

1df1.columns.levels
2# FrozenList([['A', 'B', 'C'], ['AA', 'AA.1', 'AA.2', 'AA.3', 'BB', 'CC', 'CC.1']])
3
4df1.columns.levels[0]
5# Index(['A', 'B', 'C'], dtype='object')
6
7df1.columns.levels[0][2]
8# 'C'

各レベルで重複が省かれ、かつ昇順に並び替えられた値を取得可能。

元の列の並び順が重要でない場合や、重複を排除して何か処理をしたい場合に便利かもしれない。

列指定で値を取得

1# 1列指定(Seriesとして取得)
2df1.loc[:, ("A", "BB")]
3
4# 0     6
5# 1     7
6# 2     8
7# 3     9
8# 4    10
9# Name: (A, BB), dtype: int64
10
11# 複数列指定
12df1.loc[:, [("A", "BB"), ("B", "AA")]]
13#     A   B
14#    BB  AA
15# 0  11  41
16# 1  12  42
17# 2  13  43
18# 3  14  44
19# 4  15  45

列名部分をタプルのリストとして指定することで指定した列を取得できる。

1列のみの場合はリストにしないとSeiresとして取得できる。

なお、複数列をスライスを使用して取得したい場合、列名が昇順でソートされている必要があるため、列を並び替えてもいい場合のみ使える(しかも並び替えた後の列順を考慮して指定する必要がある)

以下の記事を参照。

あわせて読みたい
【Pandas】マルチインデックスから複数列を指定する
【Pandas】マルチインデックスから複数列を指定する

列のマルチインデックスを解除する

マルチインデックスを「_(アンダースコア)」でつなげて、マルチインデックスを解除する。

1_df1 = df1.copy()
2# 列名をアンダースコアで繋げる
3_df1.columns = ["_".join(cols) for cols in df1.columns]
4print(_df1)
5#    A_AA  A_CC  A_BB  C_AA  C_CC  C_BB  B_AA  B_CC  B_CC.1  B_AA.1  B_AA.2     B_AA.3  
6# 0     1     6    11    21    26    31    41    46      51      61      66         71
7# 1     2     7    12    22    27    32    42    47      52      62      67         72
8# 2     3     8    13    23    28    33    43    48      53      63      68         73
9# 3     4     9    14    23    29    34    44    49      54      64      69         74
10# 4     5    10    15    25    30    35    45    50      55      65      70         75

マルチインデックスのまま扱った方がいい場合もあれば、マルチインデックスを解除した方がいい場合もあるため、その都度、扱いやすい方法で扱うといい。

セル結合のないデータの読み込み

以下のようなデータを読み込む。

CSVファイルだとセル結合がないため、ヘッダーが複数行のデータは、このような形になる。

1df2 = pd.read_csv("../data/df_multi_columns.csv", header=[0,1])
2print(df2)
3
4#    A Unnamed: 1_level_0 Unnamed: 2_level_0   C Unnamed: 4_level_0  \
5#   AA                 CC                 BB  AA                 CC   
6# 0  1                  6                 11  21                 26   
7# 1  2                  7                 12  22                 27   
8# 2  3                  8                 13  23                 28   
9# 3  4                  9                 14  23                 29   
10# 4  5                 10                 15  25                 30   
11
12#   Unnamed: 5_level_0   B Unnamed: 7_level_0 Unnamed: 8_level_0  \
13#                   BB  AA                 CC                 BB   
14# 0                 31  41                 46                 51   
15# 1                 32  42                 47                 52   
16# 2                 33  43                 48                 53   
17# 3                 34  44                 49                 54   
18# 4                 35  45                 50                 55   
19
20#                    D  
21#   Unnamed: 9_level_1  
22# 0                100  
23# 1                200  
24# 2                300  
25# 3                400  
26# 4                500  

列名が空白の場合、「Unnamed: N_level_M」という形式で設定される。

Nはそのレベルの自動設定された列名の通し番号。

Mはマルチインデックスのレベル。

列をMultiIndexとして取得

各列は以下のように設定されている。

1df2.columns
2# MultiIndex([(                 'A',                 'AA'),
3#             ('Unnamed: 1_level_0',                 'CC'),
4#             ('Unnamed: 2_level_0',                 'BB'),
5#             (                 'C',                 'AA'),
6#             ('Unnamed: 4_level_0',                 'CC'),
7#             ('Unnamed: 5_level_0',                 'BB'),
8#             (                 'B',                 'AA'),
9#             ('Unnamed: 7_level_0',                 'CC'),
10#             ('Unnamed: 8_level_0',                 'BB'),
11#             (                 'D', 'Unnamed: 9_level_1')],
12#            )

空白の列名がUnnamed〜に自動で設定されるため、このままでは扱いづらい。

列名を置換する

Unnamedから始まる列名では扱いにくいため、すぐ左側の列名で置換する。

この列名の置換は今回のようなデータ構造の場合の例であるため、データ構造に応じて、その都度置換方法を考えていく必要がある。

なお、そもそもデータ作成(データ読み込み)の時点でこのような状態にならない方が望ましい。

置換した列名の作成

1# 新しいマルチインデックスの列名を格納するためのリスト
2new_columns = []
3# マルチインデックスの列をループ
4for cols in df2.columns:
5    # 各列の列名を格納するためのリスト
6    col_values = []
7    # 各列の列名毎にループ
8    for idx, col in enumerate(cols):
9        # Unnamedから始まる場合は、左側(1つ前)の値に置き換える
10        if re.search("Unnamed", col):
11            col_values.append(new_columns[-1][idx])
12        else:
13            col_values.append(col)
14    new_columns.append(col_values)
15print(new_columns)
16# [['A', 'AA'],
17#  ['A', 'CC'],
18#  ['A', 'BB'],
19#  ['C', 'AA'],
20#  ['C', 'CC'],
21#  ['C', 'BB'],
22#  ['B', 'AA'],
23#  ['B', 'CC'],
24#  ['B', 'BB'],
25#  ['D', 'BB']]

ぱっと見で何してるのかよく分からないので、やはり、元データの構造を修正できるなら、その方がいい。

DataFrameの列名を再設定

1_df2 = df2.copy()
2_df2.columns = pd.MultiIndex.from_tuples(new_columns)
3print(_df2)
4#    A           C           B            D
5#   AA  CC  BB  AA  CC  BB  AA  CC  BB   BB
6# 0  1   6  11  21  26  31  41  46  51  100
7# 1  2   7  12  22  27  32  42  47  52  200
8# 2  3   8  13  23  28  33  43  48  53  300
9# 3  4   9  14  23  29  34  44  49  54  400
10# 4  5  10  15  25  30  35  45  50  55  500

一応、それっぽい列名にはなる。

スポンサーリンク
ABOUT ME
MAX
MAX
ITエンジニア、データサイエンティスト
新卒でSIerに入社し、フリーランスになってWEB系へ転向。
その後AIの世界へ足を踏み入れ、正社員に戻る。 テーブルデータの分析がメイン。
スポンサーリンク
記事URLをコピーしました