🍙

【Python】【pandas入門】値の抽出 ~locを中心とした世界~

2025/01/14に公開

脳に支障がありlocについてすぐ忘れるのでまとめておく。今回はiloc()は対象外。

サンプルデータ作成

3000年後の高校の高校課程共通科目の期末テストについてのサンプルデータを作成する。

import numpy as np
import pandas as pd

# 学校の各教科の母数
school_criteria = {
    "でんぷん高等学校": {
        "量子文学": {"mean": 50, "std": 20},
        "エンターテイメント数学論": {"mean": 60, "std": 15},
        "モンスター育成学": {"mean": 70, "std": 10},
        "ハイパー睡眠学": {"mean": 80, "std": 10},
        "エコロジカル・ディストピア学": {"mean": 45, "std": 12},
    },
    "おにぎり工業高等学校": {
        "量子文学": {"mean": 55, "std": 18},
        "エンターテイメント数学論": {"mean": 65, "std": 12},
        "モンスター育成学": {"mean": 60, "std": 15},
        "ハイパー睡眠学": {"mean": 85, "std": 8},
        "エコロジカル・ディストピア学": {"mean": 50, "std": 15},
    },
}

# 学年・組・生徒数
grades = ["1年", "2年", "3年"]
classes = ["A組", "B組", "C組"]
students_per_class = 20

# 教科
subjects = [
    "量子文学",
    "エンターテイメント数学論",
    "モンスター育成学",
    "ハイパー睡眠学",
    "エコロジカル・ディストピア学"
]

# 試験結果を格納するリスト
exam_results = []

# 各学校の試験結果を生成
for school in school_criteria:
    for grade in grades:
        for clazz in classes:
            for student_id in range(1, students_per_class + 1):
                scores = {}
                for subject in subjects:
                    mean = school_criteria[school][subject]["mean"]
                    std = school_criteria[school][subject]["std"]
                    score = int(np.clip(np.random.normal(mean, std), 0, 100))
                    scores[subject] = score

                exam_results.append({
                    "学校": school,
                    "学年": grade,
                    "学級": clazz,
                    "出席番号": student_id,
                    **scores
                })

# pandas DataFrameに変換
df_orig = pd.DataFrame(exam_results)
df_orig.head()
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 1年 A組 1 58 88 59 83 47
でんぷん高等学校 1年 A組 2 53 55 67 68 57
でんぷん高等学校 1年 A組 3 28 65 57 86 37
でんぷん高等学校 1年 A組 4 56 50 68 81 64
でんぷん高等学校 1年 A組 5 46 59 53 58 46

色々なデータ抽出

非マルチインデックス

pandas.Series

Seriesデータを作成、でんぷん高等学校の1年A組の量子文学の点数をSeries形式で取得。

ser = df_orig.loc[
    (df_orig["学校"] == "でんぷん高等学校")
    & (df_orig["学年"] == "1年")
    & (df_orig["学級"] == "A組")
    ].set_index("出席番号")["量子文学"]
ser.head()
出席番号
1    58
2    53
3    28
4    56
5    46
Name: 量子文学, dtype: int64

インデックス(出席番号)で抽出する。

ser.loc[1]
58

インデックス(出席番号)でスライスを使って抽出する。

ser.loc[3:9:2]
出席番号
3    28
5    46
7    31
9    61
Name: 量子文学, dtype: int64

インデックス(出席番号)で配列を使って抽出する。

ser.loc[[1, 3, 5]]
出席番号
1    58
3    28
5    46
Name: 量子文学, dtype: int64

値に応じて動的に抽出する。

ser.loc[ser > 60]
出席番号
6      65
9      61
15     78
19     80
20    100
Name: 量子文学, dtype: int64

pandas.DataFrame

DataFrameを作成する。

df = df_orig.loc[
    (df_orig["学校"] == "でんぷん高等学校")
    & (df_orig["学年"] == "1年")
    & (df_orig["学級"] == "A組")
    , "出席番号":].set_index("出席番号")
df.head()
出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
1 58 88 59 83 47
2 53 55 67 68 57
3 28 65 57 86 37
4 56 50 68 81 64
5 46 59 53 58 46

loc[インデックス, カラム名]で要素を抽出する。

df.loc[2, "量子文学"]
53

特定の列だけ抽出する。

df.loc[:,"量子文学"]
出席番号
1      58
2      53
3      28
4      56
5      46
6      65
7      31
8      45
9      61
10     53
11     57
12     49
13     54
14     58
15     78
16      0
17     35
18     47
19     80
20    100
Name: 量子文学, dtype: int64

特定の行だけ抽出する。

df.loc[4, :]
量子文学              56
エンターテイメント数学論      50
モンスター育成学          68
ハイパー睡眠学           81
エコロジカル・ディストピア学    64
Name: 4, dtype: int64

スライスで抽出する。

df.loc[0:20:2, "エンターテイメント数学論":"ハイパー睡眠学"]
出席番号 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学
1 88 59 83
3 65 57 86
5 59 53 58
7 64 78 87
9 61 83 82
11 72 91 77
13 100 72 62
15 47 75 73
17 86 74 85
19 47 69 88

リストで特定要素を抽出する。

df.loc[[1, 4, 6, 8, 9], ["量子文学", "モンスター育成学"]]
出席番号 量子文学 モンスター育成学
1 58 59
4 56 68
6 65 70
8 45 73
9 61 83

値に応じて動的に抽出する。

df.loc[df["ハイパー睡眠学"] > 87]
出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
16 0 79 73 89 45
18 47 93 57 91 28
19 80 47 69 88 29

複数条件の場合、演算子の優先順位の都合上、必ず各条件を()でくくる。ビット演算のほうが比較演算より優先度が高い。&| のビット演算を使用するところも注意。andor ではない。

df.loc[(df["ハイパー睡眠学"] > 87) & (df["量子文学"] > 40)]
出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
18 47 93 57 91 28
19 80 47 69 88 29

マルチインデックス

pandas.Series

マルチインデックスのSeriesを作成

ser = df_orig.set_index(["学校", "学年", "学級", "出席番号"])["量子文学"]
ser.head()
学校        学年  学級  出席番号
でんぷん高等学校  1年  A組  1       58
                  2       53
                  3       28
                  4       56
                  5       46
Name: 量子文学, dtype: int64

マルチインデックスの場合、タプルで抽出対象を指定する。

ser.loc[("でんぷん高等学校", "1年", "A組", 3)]
28

タプルの配列で複数の抽出対象を指定する。

ser.loc[
    [
        ("でんぷん高等学校", "1年", "A組", 3),
        ("でんぷん高等学校", "2年", "A組", 3)
    ]
]
学校        学年  学級  出席番号
でんぷん高等学校  1年  A組  3       28
          2年  A組  3       57
Name: 量子文学, dtype: int64

タプルをスライスする場合、事前にsort_index()を行わないとUnsortedIndexErrorが発生する。

ser.sort_index().loc[
    ("でんぷん高等学校", "1年", "A組", 19):("でんぷん高等学校", "1年", "B組", 2)
]
学校        学年  学級  出席番号
でんぷん高等学校  1年  A組  19       80
                  20      100
              B組  1        58
                  2        61
Name: 量子文学, dtype: int64

Pythonの構文上、タプル内にスライスは記載できない。

ser.loc[("でんぷん高等学校", "2年":"3年", "A組", 2:6:2)]
  Cell In[479],   line 2
    ser.loc[("でんぷん高等学校", "2年":"3年", "A組", 2:6:2)]
                             ^
SyntaxError: invalid syntax

このような場合はslice()関数が使える。

ser.sort_index().loc[
    (
        "でんぷん高等学校",
        slice("2年", "3年"),
        "A組",
        slice(2, 6, 2)
    )
]
学校        学年  学級  出席番号
でんぷん高等学校  2年  A組  2       46
                  4       59
                  6       49
          3年  A組  2       63
                  4       27
                  6       40
Name: 量子文学, dtype: int64

IndexSliceクラスを利用することで、スライス記法も使えるようになる。

ser.sort_index().loc[
    (
        "でんぷん高等学校",
        pd.IndexSlice["2年":"3年"],
        "A組",
        pd.IndexSlice[2:6:2]
    )
]
学校        学年  学級  出席番号
でんぷん高等学校  2年  A組  2       46
                  4       59
                  6       49
          3年  A組  2       63
                  4       27
                  6       40
Name: 量子文学, dtype: int64

IndexSliceクラスの添え字には bool インデックスが渡せるので、値に応じた動的な抽出ができる。

ser.sort_index().loc[
    (
        "でんぷん高等学校",
        pd.IndexSlice["2年":"3年"],
        "A組",
        pd.IndexSlice[ser.sort_index() > 75]
    )
]
学校        学年  学級  出席番号
でんぷん高等学校  2年  A組  8       92
                  13      76
                  15      78
                  18      95
          3年  A組  19      80
                  20      80
Name: 量子文学, dtype: int64

xs()メソッドも使える。ちなみに、xsとはクロスセクションのこと。クロスセクションとは核物理や散乱理論では粒子が相互作用を起こすときの反応のしやすさを示す値のことで、核分裂反応断面積や中性子捕獲断面積など色々と種類があるが、このクロスセクションはその断面積ではなく、交差系列のことだと思われる。

「おにぎり工業高等学校」のみを抽出する。

ser.xs("おにぎり工業高等学校")
学年  学級  出席番号
1年  A組  1       52
        2       61
        3       33
        4       47
        5       40
                ..
3年  C組  16      57
        17      56
        18      60
        19      42
        20      54
Name: 量子文学, Length: 180, dtype: int64

「2年」のみを抽出する。

ser.xs("2年", level=1)
学校          学級  出席番号
でんぷん高等学校    A組  1       54
                2       46
                3       57
                4       59
                5       72
                        ..
おにぎり工業高等学校  C組  16      75
                17      54
                18      55
                19      33
                20      46
Name: 量子文学, Length: 120, dtype: int64

「C組」のみを抽出する。

ser.xs("C組", level=2)
学校          学年  出席番号
でんぷん高等学校    1年  1       68
                2       92
                3       58
                4       55
                5       54
                        ..
おにぎり工業高等学校  3年  16      57
                17      56
                18      60
                19      42
                20      54
Name: 量子文学, Length: 120, dtype: int64

出席番後「13」のみを抽出する。

ser.xs(13, level=3)
学校          学年  学級
でんぷん高等学校    1年  A組    54
                B組     5
                C組    46
            2年  A組    76
                B組    44
                C組    26
            3年  A組    55
                B組    39
                C組    20
おにぎり工業高等学校  1年  A組    69
                B組    78
                C組    36
            2年  A組    23
                B組     4
                C組    57
            3年  A組    78
                B組    74
                C組    72
Name: 量子文学, dtype: int64

drop_level=Falseで、出力時に抽出対象としたインデックスの情報を残す。

ser.xs(13, level=3, drop_level=False)
学校          学年  学級  出席番号
でんぷん高等学校    1年  A組  13      54
                B組  13       5
                C組  13      46
            2年  A組  13      76
                B組  13      44
                C組  13      26
            3年  A組  13      55
                B組  13      39
                C組  13      20
おにぎり工業高等学校  1年  A組  13      69
                B組  13      78
                C組  13      36
            2年  A組  13      23
                B組  13       4
                C組  13      57
            3年  A組  13      78
                B組  13      74
                C組  13      72
Name: 量子文学, dtype: int64

タプルで複数階層を指定して抽出できる。

ser.xs(("おにぎり工業高等学校", "C組", 8), level=[0, 2, 3], drop_level=False)
学校          学年  学級  出席番号
おにぎり工業高等学校  1年  C組  8       57
            2年  C組  8       54
            3年  C組  8       42
Name: 量子文学, dtype: int64

値に応じた動的な抽出を行う。

ser.loc[ser > 90]
学校          学年  学級  出席番号
でんぷん高等学校    1年  A組  20      100
                B組  18       91
                C組  2        92
            2年  A組  8        92
                    18       95
おにぎり工業高等学校  1年  A組  8        96
            2年  A組  11      100
Name: 量子文学, dtype: int64

複数条件での値に応じた抽出。

ser.loc[(ser > 90) | (ser < 6)]
学校          学年  学級  出席番号
でんぷん高等学校    1年  A組  16        0
                    20      100
                B組  13        5
                    18       91
                C組  2        92
            2年  A組  8        92
                    9         0
                    18       95
おにぎり工業高等学校  1年  A組  8        96
            2年  A組  11      100
                B組  13        4
Name: 量子文学, dtype: int64

Seriesの場合、マルチインデックスを「,」区切りで指定することができる。データ構造によっては、ぱっと見DataFrameの指定とごちゃるので、この方法は紛らわしいと思う。

ser.sort_index().loc["おにぎり工業高等学校", "2年", "C組", 4:7]
学校          学年  学級  出席番号
おにぎり工業高等学校  2年  C組  4       54
                    5       66
                    6       47
                    7       62
Name: 量子文学, dtype: int64

pandas.DataFrame

Seriesと基本、変わらない。マルチインデックスのDataFrameを作成

df = df_orig.set_index(["学校", "学年", "学級", "出席番号"])
df.head()
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 1年 A組 1 58 88 59 83 47
2 53 55 67 68 57
3 28 65 57 86 37
4 56 50 68 81 64
5 46 59 53 58 46

タプルで要素を指定して抽出する。

df.loc[("でんぷん高等学校", "1年", "A組", 3)]
量子文学              28
エンターテイメント数学論      65
モンスター育成学          57
ハイパー睡眠学           86
エコロジカル・ディストピア学    37
Name: (でんぷん高等学校, 1年, A組, 3), dtype: int64

タプルの配列で複数の抽出対象を指定する。

df.loc[
    [
        ("でんぷん高等学校", "1年", "A組", 3),
        ("でんぷん高等学校", "2年", "A組", 3)
    ]
]
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 1年 A組 3 28 65 57 86 37
2年 A組 3 57 53 92 85 47

タプルをスライスする場合、事前にsort_index()を行わないとUnsortedIndexErrorが発生する。

df.sort_index().loc[
    ("でんぷん高等学校", "1年", "A組", 19):("でんぷん高等学校", "1年", "B組", 2)
]
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 1年 A組 19 80 47 69 88 29
20 100 47 62 76 50
B組 1 58 49 66 79 65
2 61 48 73 65 40

slice()関数を使う。

df.sort_index().loc[
    (
        "でんぷん高等学校",
        slice("2年", "3年"),
        "A組",
        slice(2, 6, 2)
    )
]
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 2年 A組 2 46 23 70 69 25
4 59 74 78 68 55
6 49 79 63 79 42
3年 A組 2 63 35 68 65 55
4 27 77 75 78 58
6 40 53 73 78 52

IndexSliceクラスを利用する。

df.sort_index().loc[
    (
        "でんぷん高等学校",
        pd.IndexSlice["2年":"3年"],
        "A組",
        pd.IndexSlice[2:6:2]
    )
]
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 2年 A組 2 46 23 70 69 25
4 59 74 78 68 55
6 49 79 63 79 42
3年 A組 2 63 35 68 65 55
4 27 77 75 78 58
6 40 53 73 78 52

IndexSliceクラスで値に応じた動的な抽出をする。

df.sort_index().loc[
    (
        "でんぷん高等学校",
        pd.IndexSlice["2年":"3年"],
        "A組",
        pd.IndexSlice[df.sort_index()["量子文学"] > 75]
    )
]
学校 学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
でんぷん高等学校 2年 A組 8 92 59 76 83 44
13 76 34 58 81 57
15 78 75 78 100 25
18 95 48 63 84 46
3年 A組 19 80 63 71 96 39
20 80 40 79 79 36

xs()メソッドを使う。

df.xs("おにぎり工業高等学校")
学年 学級 出席番号 量子文学 エンターテイメント数学論 モンスター育成学 ハイパー睡眠学 エコロジカル・ディストピア学
1年 A組 1 52 73 86 97 22
2 61 64 46 100 54
3 33 83 46 91 52
4 47 66 77 78 32
5 40 82 52 60 68
... ... ... ... ... ... ... ...
3年 C組 16 57 66 85 93 53
17 56 62 63 91 42
18 60 68 13 84 34
19 42 72 67 100 38
20 54 84 55 96 46

DataFrameの場合、xs()メソッドに引数axis=1を指定することで、列のSerirsを取得できる

df.xs("量子文学", axis=1)
学校          学年  学級  出席番号
でんぷん高等学校    1年  A組  1       58
                    2       53
                    3       28
                    4       56
                    5       46
                            ..
おにぎり工業高等学校  3年  C組  16      57
                    17      56
                    18      60
                    19      42
                    20      54
Name: 量子文学, Length: 360, dtype: int64

xs()メソッドで行と列を指定するが、locじゃダメなのか?

df.xs(("B組", 10), level=(2, 3), axis=0, drop_level=False).xs("モンスター育成学", axis=1)
学校          学年  学級  出席番号
でんぷん高等学校    1年  B組  10      63
            2年  B組  10      60
            3年  B組  10      46
おにぎり工業高等学校  1年  B組  10      74
            2年  B組  10      61
            3年  B組  10      55
Name: モンスター育成学, dtype: int64

まとめ

  • loc[]には、インデックスの型のリテラル、リスト、スライスが利用可能。
  • loc[]で複数条件を指定する場合は各条件を()でくくる。ビット演算(&|)を用いる。
  • マルチインデックスの場合
    • タプルでインデックスを指定する。
    • タプル内でスライスを使いたい場合slice()関数やpandas.IndexSliceクラスが使える。
    • スライスなどで複数行抽出する場合はsort_index()でソートする。

Discussion