日本の大学データ#

日本の大学のデータを使って、Polars の基本操作をひととおり見ていきます。読み込み、列の操作、行の絞り込み、グループ集計、グラフまでを、実際のデータで確認します。

まず polars を pl という別名でインポートします。この別名は慣例です。続いて CSV を読み込み、先頭2行を表示します。

ファイル読み込み#

import polars as pl
import hvplot.polars
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 で確認できます。列名をキー、データ型を値とする辞書が返ります。

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 = 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 に2つの変換を適用し、新しいデータフレーム df3 を作ります。

  • found — 文字列から日付型へ

  • type_jp と difficulty_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() で取り出して確認します。

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"

条件は &(AND)、|(OR)、~(NOT)で組み合わせます。次のコードは、大阪にあって偏差値が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() — 偏差値の降順に並べる

  • drop_nulls() — 偏差値が NULL の行を落とす

  • select() — 大学名と偏差値だけにする

  • 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
"国立"
"公立"
"私立"

カテゴリ列ごとに集計するときは 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

件数だけでなく、偏差値の最小値・中央値・平均値・最大値も同時に求められます。キーワード引数で出力列名を指定します。

(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
"私立"62635.038.540.80103470.0
"公立"10140.050.050.04239169.7
"国立"8635.751.752.91707370.5

グラフ#

Polars 自体には描画機能はありませんが、hvplot をインポートすると .hvplot から対話的なグラフを作れます。地図に重ねるときは geoviews も必要です。

conda install -c conda-forge hvplot geoviews

棒グラフ#

次のコードは、都道府県ごとに大学の数を数え、多い順に並べて棒グラフにします。描画用のメソッドは .hvplot の下にあります。

  • x と y に軸のラベルを指定する

  • rot=90 で X 軸のラベルを縦向きにする

  • 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)
.hvplot.bar(
    x=x_label, y=y_label, 
    rot=90, frame_width=900
)
)

スタック棒グラフ#

次は都道府県だけでなく、大学の種類(国立・公立・私立)でも分けて積み上げます。先のコードとの違いは3点です。

  1. group_by() に type_jp を足し、都道府県 × 種類でグループ化する。

  2. 並べ替えは「都道府県ごとの合計」で行う。over() で式の中にグループ集計を書ける(over() の使い方)。

  3. 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)
.hvplot.bar(
    x=x_label, y=y_label, by="type_jp", 
    stacked=True, rot=90, 
    frame_width=900, frame_height=450
)
)

経度と緯度に点を打ち、大学の種類で色分けして地図に重ねます。

  • by="type_jp" — 種類ごとに色を分ける

  • hover_cols=['name_jp'] — マウスを乗せたときに大学名を出す

  • tiles=True — 背景に地図タイルを敷く

geo=True を併せて指定すると、geoviews の投影設定と競合して描画に失敗することがあります。tiles=True だけで地図になります。

df3.hvplot.points(
    "longitude",
    "latitude",
    by="type_jp",
    hover_cols=["name_jp"],
    tiles=True,
    frame_width=600,
)