日本の大学データ#

日本の大学に関するデータを用いて、Polarsライブラリの基本的な使い方を解説します。Polarsは、高性能かつ効率的なデータ操作を可能にするRust製のDataFrameライブラリであり、大量のデータを扱う際に特に有用です。以下のセクションでは、データの読み込みから基本的な操作まで、Polarsの基本機能について順を追って説明します。

次のプログラムは、Polarsライブラリを使用してCSVファイルを読み込み、その最初の2行を表示します。Polarsライブラリをplというエイリアスでインポートするのは一般的です。

ファイル読み込み#

import polars as pl
df = pl.read_csv('data/japanese_universities.csv')
df.head(2)
shape: (2, 22)
codenamename_jptypetype_jpaddresspostal_codephonestatestate_jplatitudelongitudefoundfaculty_countdepartment_counthas_gradhas_remotereview_ratingreview_countdifficulty_SDdifficulty_rank
i64strstrstrstrstrstrstrstrstrstrf64f64stri64i64boolboolf64f64f64str
0"F101110100010""Hokkaido University""北海道大学""National""国立""北海道札幌市北区北8条西5丁目""060-0808""011-716-2111""Hokkai Do""北海道"43.070446141.347153"1876-08"3378truefalse4.161389.060.4"A"
1"F101110100029""Hokkaido University of Educati…"北海道教育大学""National""国立""北海道札幌市北区あいの里5条3-1-3""002-8501""011-778-0206""Hokkai Do""北海道"43.170498141.393753"1943-04"38truefalse3.79544.047.1"D"

columnsでデータフレームのすべての列名をリストとして取得できます。

列の情報#

df.columns
['',
 'code',
 'name',
 'name_jp',
 'type',
 'type_jp',
 'address',
 'postal_code',
 'phone',
 'state',
 'state_jp',
 'latitude',
 'longitude',
 'found',
 'faculty_count',
 'department_count',
 'has_grad',
 'has_remote',
 'review_rating',
 'review_count',
 'difficulty_SD',
 'difficulty_rank']

列名とそのデータ型を取得するには、schemaを使用します。schemaは、列名をキー、データ型を値とする辞書を返します。

df.schema
Schema([('', Int64),
        ('code', String),
        ('name', String),
        ('name_jp', String),
        ('type', String),
        ('type_jp', String),
        ('address', String),
        ('postal_code', String),
        ('phone', String),
        ('state', String),
        ('state_jp', String),
        ('latitude', Float64),
        ('longitude', Float64),
        ('found', String),
        ('faculty_count', Int64),
        ('department_count', Int64),
        ('has_grad', Boolean),
        ('has_remote', Boolean),
        ('review_rating', Float64),
        ('review_count', Float64),
        ('difficulty_SD', Float64),
        ('difficulty_rank', String)])

列操作#

drop()メソッドを使って、特定の列を削除します。Polarsのほとんどのメソッドは新しいデータフレームを返すため、変数df2に新しいデータフレームを保存します。

df2 = df.drop('', 'code', 'name', 'type', 'state', 'postal_code', 'phone')
df2.head(2)
shape: (2, 15)
name_jptype_jpaddressstate_jplatitudelongitudefoundfaculty_countdepartment_counthas_gradhas_remotereview_ratingreview_countdifficulty_SDdifficulty_rank
strstrstrstrf64f64stri64i64boolboolf64f64f64str
"北海道大学""国立""北海道札幌市北区北8条西5丁目""北海道"43.070446141.347153"1876-08"3378truefalse4.161389.060.4"A"
"北海道教育大学""国立""北海道札幌市北区あいの里5条3-1-3""北海道"43.170498141.393753"1943-04"38truefalse3.79544.047.1"D"

with_columnsメソッドは、データフレームに対して新しい列を追加したり、既存の列を変換するために使用します。このメソッドを使うと、複数の列に対して同時に操作を行うことができます。次のコードでは、df2に対してwith_columnsメソッドを使って次のような演算を行い、新しいデータフレームdf3を作成します。

  • found列を文字列から日付型に変換します。

  • type_jpdifficulty_rank列を文字列型からカテゴリカル型に変換します。カテゴリカル型は、文字列型に比べてメモリ効率が良く、分析に便利です。

with_columnsに渡した引数は演算の結果ではなく、演算そのものを表す演算式です。演算式の詳細について

df3 = df2.with_columns(
    pl.col('found').str.to_date("%Y-%m"),
    pl.col('type_jp', 'difficulty_rank').cast(pl.Categorical)
)

確認のため、select()メソッドを使ってデータ型変換が行われた列を選択し、先頭の5行を表示します。

df3.select('name_jp', 'found', 'type_jp', 'difficulty_rank').head()
shape: (5, 4)
name_jpfoundtype_jpdifficulty_rank
strdatecatcat
"北海道大学"1876-08-01"国立""A"
"北海道教育大学"1943-04-01"国立""D"
"室蘭工業大学"1897-05-01"国立""F"
"小樽商科大学"1910-03-01"国立""C"
"帯広畜産大学"1941-04-01"国立""B"

Tip

select()with_columns()は、データフレームでの列操作に使われるメソッドです。違いは、select()は選択された列のみを出力し、その他の列は含まれませんが、with_columns()は操作を行った列だけでなく、操作されなかった列もそのまま出力します。

行フィルタ#

filter()メソッドで指定した条件を満たす行を選択することができます。このメソッドは新しいデータフレームを返し、元のデータフレームは変更されません。次のコードはdifficulty_SD列(偏差値)の値が65より大きい行を選択しています。

df3.filter(
    pl.col('difficulty_SD') > 65
)
shape: (8, 15)
name_jptype_jpaddressstate_jplatitudelongitudefoundfaculty_countdepartment_counthas_gradhas_remotereview_ratingreview_countdifficulty_SDdifficulty_rank
strcatstrstrf64f64datei64i64boolboolf64f64f64cat
"東京大学""国立""東京都文京区本郷7-3-1""東京都"35.714146139.7632141877-04-0125132truefalse4.342206.070.5"S"
"一橋大学""国立""東京都国立市中2-1""東京都"35.694389139.4436491920-04-011015truefalse4.26432.067.9"S"
"京都大学""国立""京都府京都市左京区吉田本町""京都府"35.026962135.7819671886-04-012894truefalse4.21434.065.6"S"
"国際教養大学""公立""秋田県秋田市雄和椿川字奥椿岱""秋田県"39.62706140.1811072004-04-0124truefalse4.18100.069.7"S"
"慶應義塾大学""私立""東京都港区三田2-15-45""東京都"35.649147139.7428281890-01-012859truetrue4.192682.066.3"S"
"日本医科大学""私立""東京都文京区千駄木1-1-5""東京都"35.721233139.7584531904-04-0122truefalse3.9931.070.0"S"
"早稲田大学""私立""東京都新宿区戸塚町1-104""東京都"35.710083139.7222441902-10-013594truetrue4.144280.065.7"S"
"国際基督教大学""私立""東京都三鷹市大沢3-10-2""東京都"35.686474139.5274811953-03-0126truefalse4.43206.068.0"S"

Pythonのビット演算子~, |, &を使って、複数の条件をANDやORで組み合わせて行を選択することができます。次のコードは、大阪にあり偏差値が55より高い大学を抽出します。

df3.filter(
    (pl.col('difficulty_SD') > 55) & (pl.col('state_jp').str.starts_with("大阪"))
)
shape: (3, 15)
name_jptype_jpaddressstate_jplatitudelongitudefoundfaculty_countdepartment_counthas_gradhas_remotereview_ratingreview_countdifficulty_SDdifficulty_rank
strcatstrstrf64f64datei64i64boolboolf64f64f64cat
"大阪大学""国立""大阪府吹田市山田丘1-1""大阪府"34.819496135.5211641917-04-012675truefalse4.121840.061.3"A"
"大阪医科薬科大学""私立""大阪府高槻市大学町2-7""大阪府"34.85186135.6244051904-05-0169truefalse4.119.057.0"B"
"関西大学""私立""大阪府吹田市山手町3-3-35""大阪府"34.769669135.5101621904-01-012944truefalse3.952661.055.6"B"

メソッドチェーン#

メソッドチェーンとは、複数のメソッドを連続して呼び出すプログラミングスタイルのことです。Polarsライブラリでは、データフレームに対して複数の操作を直感的に連鎖させるためにこのスタイルをよく使用します。メソッドチェーンを使うことで、コードが読みやすくなり、一連のデータ処理操作を一つの流れとして視覚的に把握することができます。

以下のコードは、データフレームdf3に対して一連の操作をメソッドチェーンを使って行っています:

  • filter()type_jp列が「国立」である行をフィルタリングします。

  • sort()difficulty_SD列に基づいて、データを降順(大きい順)にソートします。

  • drop_nulls()difficulty_SD列にNULL(欠損値)が含まれる行を削除します。

  • select()name_jp列とdifficulty_SD列だけを選択します。

  • head()で最初の5行を取得します。

(df3
.filter(pl.col.type_jp == '国立')
.sort('difficulty_SD', descending=True)
.drop_nulls('difficulty_SD')
.select('name_jp', 'difficulty_SD')
.head(5)
)
shape: (5, 2)
name_jpdifficulty_SD
strf64
"東京大学"70.5
"一橋大学"67.9
"京都大学"65.6
"東京工業大学"65.0
"浜松医科大学"64.4

グループ処理#

次のコードで、type_jp列のユニークな値を取得します。結果から、国立、公立、私立の3種類の大学のデータがあることがわかります。

df3.select(pl.col('type_jp').unique())
shape: (3, 1)
type_jp
cat
"国立"
"公立"
"私立"

type_jp列のような分類列に対して、グループ化し、各個グループのデータを集計を行うために、group_by()agg()を使います。

  • group_by(): 指定した列の値ごとにデータをグループ化します。

  • agg(): グループ化した後に、各グループに対して特定の集計処理(例えば、カウント、合計、平均など)を実行します。

次の例ではpl.len()で各個グループの長さを取得します。

df3.group_by('type_jp').agg(pl.len())
shape: (3, 2)
type_jplen
catu32
"国立"86
"公立"101
"私立"626

次のコードは、各グループのサイズに加えて、偏差値列(difficulty_SD)の最小値、中間値、平均値、最大値を求めます。キーワード引数を使用して列名を指定します。

(df3
.group_by('type_jp')
.agg(
    count = pl.len(),
    min = pl.col('difficulty_SD').min(),
    median = pl.col('difficulty_SD').median(),
    mean = pl.col('difficulty_SD').mean(),
    max = pl.col('difficulty_SD').max(),
)
)
shape: (3, 6)
type_jpcountminmedianmeanmax
catu32f64f64f64f64
"国立"8635.751.752.91707370.5
"公立"10140.050.050.04239169.7
"私立"62635.038.540.80103470.0

グラフ#

Polars自体にはグラフを出力する機能がありませんが、次のコマンドでhvplotgeoviewsをインストールすれば、インタラクティブなグラフを作成することができます。

conda install hvplot geoviews

棒グラフ#

以下のプログラムは、都道府県ごとにグループ化し、各都道府県にある大学の数を計算して、降順に並べ替え、最終的に棒グラフを作成します。グラフ作成関連のメソッドは全部.plotネーミングスペースの下にあります。bar()メソッドの各個引数は以下のようです。

  • X軸にx_label(都道府県)、Y軸にy_label(大学の数)を設定します。

  • rot=90はX軸のラベル(都道府県名)を90度回転させ、縦向きに表示します。

  • frame_width=900はグラフの幅を900ピクセルに設定します。

x_label = "都道府県"
y_label = "大学の数"
(df3
.group_by(pl.col("state_jp").alias(x_label))
.agg(
    pl.len().alias(y_label)
)
.sort(y_label, descending=True)
.plot.bar(
    x=x_label, y=y_label, 
    rot=90, frame_width=900
)
)

スタック棒グラフ#

以下のプログラムは、先のプログラムに比べて都道府県ごとだけでなく、さらに大学の種類(国立、公立、私立)ごとにもグループ化し、スタックされた棒グラフとして視覚化します。上のプログラムとの主な違いについて説明します。

  1. group_by()type_jp列を追加します。これで、都道府県と大学の種類でグループ化します。

  2. 並び替えの時、各都道府県内で大学の種類ごとの数の合計に基づいて降順にソートします。これにより、都道府県全体の大学の数が多い順に並べ替えられます。ここでover()を使って、演算式内部でグループ化処理を行います。over()の使い方

  3. bar()の引数について、by="type_jp"によって大学の種類ごとに異なる色で表示され、各都道府県内での大学の種類の分布が視覚的にわかりやすくなります。stacked=Trueにより、大学の種類ごとの棒グラフが積み上げられて表示されます。

(df3
.group_by(
    pl.col("state_jp").alias(x_label), 
    "type_jp"
)
.agg(
    pl.len().alias(y_label)
)
.sort(pl.col(y_label).sum().over(x_label), descending=True)
.plot.bar(
    x=x_label, y=y_label, by="type_jp", 
    stacked=True, rot=90, 
    frame_width=900, frame_height=450
)
)

地図付き散布グラフ#

次のプログラムは、経度と緯度に基づいてポイントプロットを作成し、大学の種類に基づいて色分けし、インタラクティブな地理情報を含むマップとして表示します。

  • by="type_jp": 大学の種類(type_jp)ごとにポイントを色分けします。これにより、異なる種類の大学が異なる色で表示されます。

  • hover_cols=['name_jp']: ポイントにマウスをホバーしたときに表示する情報として、大学名(name_jp)を指定します。これにより、ユーザーがポイントにカーソルを合わせたときに大学名が表示されます。

  • tiles=True: 地図タイルを有効にします。これにより、背景に地図が表示され、ポイントの地理的な位置が視覚的にわかりやすくなります。

  • geo=True: 地理情報プロットを有効にします。これにより、経度と緯度に基づいてポイントが正確に地図上に配置されます。

df3.plot.points(
    'longitude', 'latitude', 
    by="type_jp", 
    hover_cols=['name_jp'],
    tiles=True, 
    geo=True, 
    frame_width=600, 
)
C:\micromamba\envs\jupyter4\Lib\site-packages\holoviews\core\data\pandas.py:239: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
  dataset.data.groupby(group_by, sort=False)]