コンテンツにスキップ

第04回 Python統計処理入門(Colab / Pandas / NumPy)

10/20(火)Ⅱ講 10:35〜12:15・B402教室

計算は機械に、判断は人間に ―― 今日から、あなたがコードを書く


今日のリンク

この回のゴール

  • データを「表(DataFrame)」として読み込み、列を取り出せる
  • 第2・3回で手計算した代表値・ばらつきを、コードで再現できる
  • describe() / groupby() / isna() / ヒストグラムで、基礎集計を自分で実行できる

到達目標との対応:G3(Pythonで自律的に解析)の本丸。第5回以降の相関・推定・検定は、すべてこの上に乗る。


前回の答案から

第3回の記述(パートB・60点)は、満点が24名(49%)/平均57.0点だった。 今回から「今日のColabで確認した数値を最低1つ引用すること」を実際に適用している。

目立った型を4つ挙げる。いずれも、今日の内容に直接つながる。

① 自分のノートの数値を引いていない(13名・いちばん多い)

内容は正しいのに点が止まった人がほとんどだった。象徴的だったのがこれ。

仮に生の体重の歪度が正の値であれば、右側に長い裾を持つ分布であることが分かる

ノートに +7.60 と出ているのに、仮定の話にしてしまっている。 「生の体重の歪度は+7.60だった」と書けば、今日のデータについての事実になる。 自分で出した数字は、仮定ではなく事実として使える。

惜しい形がもう1つ。「妊娠期間の歪度がほぼ0なのに正規分布ではなかった」―― 指摘としては今回いちばん鋭いのに、「ほぼ0」で止めたので通過しなかった。 −0.06 と書けばよかっただけである。

② 因果が逆 ―― 「正規分布を当てはめたから歪む」

当てはめたから歪むのではない。もともと歪んでいる(+7.60)から当てはまらない。 歪みはデータ側の性質で、正規分布を持ち出す前からそこにある。

なお、平均−1SD=−7,254g(5,881−13,135)という引き算そのものは、正規分布を仮定しなくてもできる。 正規分布が関わるのは、その先の読み方である。「平均±1SDの中に約68%が入る」は左右対称の正規分布での話で、 右に強く歪んだこの体重データを、そう読むことはできない。

正しい順序:もともと右に歪んでいる → だから左右対称の正規分布が合わない → 「平均±1SDに約68%」という読み方ができない(下限が−7,254gという、ありえない値になる)

③ SDの値から外れ値の有無を断定した

SD=40という値は、極端に重い or 軽い製品の外れ値の存在によって大きくなっている

SDの値だけからは、外れ値があるのかどうかは決まらない。 「一部に極端な値がある」場合でも、「平均500gを中心に全体が広く散らばっている」場合でも、 SDが40gになることはある。 見分けるには、値の散らばり方そのものを確かめるしかない。ヒストグラム(今日の 8. グラフを1行で)・ 箱ひげ図・生データの点検が、まさにそのための道具である。

④ 数値の取り違え ―― これが今日いちばん効く

生のスケールの体重の歪みは 7.254 であった

歪度 +7.60(単位なしの指標)と、平均−1SD=−7,254g(単位はg)が混ざっている。 7.254 という数字はノートのどこにもない。

数値を引くときは「何の数字で、単位は何か」を一緒に書く。

今日につながること

今日からは、数値をコードで出す。計算は一瞬で終わる。だが ――

コードは数字を出してくれるが、それが何の数字かは言ってくれない。

df["体重g"].mode() が 992.4 を返しても、それが「意味のある最頻値」かどうかは画面には出ない。 機械が出した数字に、何の数字で、単位は何で、それは信じてよいのかというラベルを貼るのが人間の仕事である。 これが「計算は機械に、判断は人間に」の中身であり、今日の課題B1で聞いていることでもある。


0. 今日から方式が変わる

第1〜3回は「読んで▶を押すだけ」だった。今日からは、空欄 ____ を自分で埋める。とはいえコードの大半は用意してあるので、入れるのは列名やメソッド名だけ。

エラーは失敗ではない

エラーメッセージは「ここが違うよ」というお知らせ。KeyError は列名のスペル違い、SyntaxError はカッコや全角・半角のことが多い。落ち着いて読めば、たいてい原因が書いてある。

ルール:エラーが直らないときは、AIに聞いてよい

この授業では、エラーが出たときにAIに聞くことを認める。次のどちらでもよい。

  • 🤖 学内のCopilotを開く(大学のアカウントでサインイン) → エラーメッセージをそのままコピーして貼り、「これはどういう意味ですか」と聞く
  • Colabのエラー回復(エラー表示の近くに出る、Geminiにエラーを説明させるボタン)を使う

条件は2つだけ。

  1. エラーメッセージを自分でも読む。 まず読んでから聞く
  2. 直ったコードを、何をどう直したのか自分の言葉で言えるようにする

課題B2は「詰まった点を1つ挙げ、何が原因で、どう解決したか」を書く問題である。 AIに聞いて解決した場合は、そう書いてよい(何を聞いて、何が分かったか)。減点はしない。


1. データは「表」= DataFrame

CSVを読み込むと、Excelのような表になる。Pandasでは、このような表形式のデータを DataFrame として扱い、慣習的に df という名前を付ける。

df = pd.read_csv(URL)     # 表を読み込む
df.shape                  # (行数=種数, 列数)
list(df.columns)          # 列名の一覧
df.head()                 # 先頭5行

「行=1つの種」「列=体重・妊娠期間などの項目」。これがデータ分析の基本の形だ。


2. 列を取り出す

1列を取り出すには df["列名"] と書く。

df["体重g"]      # 体重の列

列名は正確に

df["テスト店"] のように1文字でも違うと KeyError。list(df.columns) で正しい綴りを確認するくせをつけよう。


3. 代表値をコードで(第2回の再現)

第2回で手計算した3つの代表値は、メソッド1つで出る。

df["体重g"].mean()        # 平均   → 5880.9
df["体重g"].median()      # 中央値 → 3006.0
df["体重g"].mode()        # 最頻値 → 992.4   ← この値の読み方に注意
df["一腹産子数"].mode()   # 最頻値 → 1.01    ← これも読み方に注意
df["科"].mode()           # 最頻値 → オナガザル科

第2回で電卓を叩いて出した値と、同じになるはず。違いは速さと正確さだけ。

最頻値が 992.4 g ―― 計算は通る。では、何を表しているのか

.mode() が返すのは、データに最も多く記録されている値である。体重のような連続量では、 測定の精度や丸め、データのまとめ方によって、同じ値が何回現れるかが変わる。

このデータでは、体重の記録がある265種のうち 992.4 g が 6種 でいちばん多かった (255通りある値のうち、2回以上現れるものは6つしかない)。 その6種は、いずれも同じ Callicebus 属である。なぜ同じ値になっているのかは、 このCSVだけでは分からない(元の文献が共通、まとめ方、丸め……いろいろ考えられる)。

中央値 3,006 g から離れていること自体は、誤りの証拠ではない。歪んだ分布では、 中央値と最頻値が別の位置を指すのは普通のことである。言えるのは、 992.4 g がいちばん多く記録されていても、それが体重分布の山や典型的な体重を表すとはかぎらないということだ。 ヒストグラムなども確かめて判断しよう。

第3回の偏差値159と同じで、計算が通ることと、その数字をどう読めるかは別である。

一腹産子数が 1.01 ―― 1行が何を表しているか

一腹産子数 の最頻値は 1.01。子の数なのに小数になるのは、この列が 種ごとに整理・調整された代表値だからである(出典のPanTHERIAは、複数の文献の値を 条件をそろえてから、種ごとに1つの値にまとめている)。この表では1.01が 74種 に記録されている。

だから「1.01頭生まれる」という意味ではない。また、ここで数えているのは種であって 出産そのものではないので、この集計だけで「霊長類の出産はふつう1頭」を直接確かめたことにもならない。 数値を解釈するときは、1行が何を表しているかにも注意する。

いっぽう、同じ値が繰り返し現れる列なら、最頻値はそのまま読める。df["科"].mode() は オナガザル科 (df["科"].value_counts() で数えると132種)。これは「この表にいちばん多く入っている科」と、そのまま解釈できる。


4. ばらつきをコードで(第3回の再現)

df["妊娠期間日"].std()   # 標準偏差 → 37.8(日)
df["妊娠期間日"].var()   # 分散     → 1427.2(日²)

第3回の「SD ≈ 37.8日」がそのまま出る。分散はSDを2乗した量なので、単位も2乗になって 1,427.2 日² である。 数字だけでなく単位まで一緒に見る、という第3回の約束は、コードでも同じ。 なお .std() / .var() は、第3回で確認したとおり既定で分母に n−1 を使う(ddof=0 と書けば n になる)。

桁をそろえて表示したいときは、print("標準偏差", round(df["妊娠期間日"].std(), 1), "日") のように 組み込みの round(数値, 桁数) で囲む。


5. describe() ―― 一気に要約

df["体重g"].describe()

件数・平均・SD・最小・四分位数・最大が一度に出る。手計算なら数十分の作業が一瞬で終わる。


6. グループごとに集計(groupby)

「科ごとの体重平均」のようなグループ別集計は、groupby で書く。

df.groupby("科")["体重g"].mean()
# ヒト科 70526 / オナガザル科 7829 / クモザル科 7127 …

groupby("グループにする列")["集計する列"].集計関数() という形。これは第9回(分散分析)への布石でもある。


7. 欠損を確認

df.isna().sum()   # 列ごとの欠損(値が記録されていない)の数
# 初産日齢: 273 / 出産間隔日: 267 / 新生児体重g: 259 …(体重gですら111種が空欄)

本物の研究データは、こういう姿をしている。体重ですら111種が空欄で、初産日齢にいたっては273種が空欄である。 ここでいう「欠損」は、このデータ表に値が記録されていないという意味である(一度も測られていないのか、 文献から値を集められなかったのかまでは、この表からは分からない)。 現実の研究データには、こうした欠損が含まれていることが多い。

.mean() や .describe() は、その列の欠損を除いて計算される。今日の体重の平均 5,880.9 g は、 記録のある265種から出した平均である(describe() の count が265なのは、そのためだ)。 欠損している種に何か特徴があれば、値がある種だけを集めた結果は、全体とずれる可能性がある。 第6回で「この偏りが何を意味するか」を詳しく扱う。


8. グラフを1行で

plt.hist(df["体重g"].dropna(), bins=40)

第2・3回で見たヒストグラムも、コードなら1行。日本語が□□に化けるときは、冒頭の japanize-matplotlib を実行したか確認する。


まとめ

やりたいこと コード
列を取り出す df["列名"]
平均・中央値・最頻値 .mean() .median() .mode()(.mode() は値の重複・丸めの影響に注意)
標準偏差・分散 .std() .var()
まとめて要約 .describe()
グループ別集計 df.groupby("列")["列"].mean()
欠損の確認 df.isna().sum()
ヒストグラム plt.hist(df["列"])

手で求めた値と、コードで求めた値は同じ。違うのは速さと正確さ。 計算は機械に任せ、人間は「どの数字を信じるか」の判断に集中する。 コードは直感を持たない。だから直感が系統的に外れる場面でも、同じ手続きを同じ向きに実行する。第1回のモンティ・ホールを1万回試せたのは、飽きも思い込みもない相手だったからだ。


今日の課題(Moodle提出)

配点:100点(選択40点 + 記述60点)/ 提出先:Moodle / 提出期限:次回授業の開始時刻まで

Colab 04_python_intro.ipynb の ____ をすべて埋めて実行し、出力された数値を見て答えること。


パートA:選択・数値問題(各10点 ×4=40点)

A1. df["体重g"].mean() を実行して出た平均値(小数第1位まで)を入力しなさい。 (半角数字で。例:1234.5)

A2. df.groupby("科")["体重g"].mean() を実行したとき、体重の平均が最も大きかった科はどれか。

  • (ア) ヒト科 (イ) オナガザル科 (ウ) コビトキツネザル科

A3. df.isna().sum() を実行したとき、体重g の欠損(未測定)の数はいくつか。(半角数字)

A4. df["体重g"].describe() の count(有効な種数) はいくつか。(半角数字)


パートB:記述問題(AIフィードバック対象・60点)

【全記述に共通・重要】自分の出力を引用すること

各記述には、今日あなたのノートが出力した具体的な数値を最低1つ引用すること(例:「平均は5880.9と出た」「科別ではヒト科が70,526gで最大だった」「体重の欠損は111種あった」)。 引用がない記述は満点になりません。

B1.(30点) 第2回・第3回では電卓などで代表値やばらつきを手計算した。今日は同じ値をコードで求めた。両者を比べて、「計算は機械に、判断は人間に」とはどういうことかを、自分の言葉で説明しなさい。手計算とコードはそれぞれ何が得意で、人間が集中すべきことは何か。

B2.(30点) 今日コードを書いていて詰まった点(エラーや迷い)を1つ挙げ、何が原因で、どう解決したか(または何を確認したか)を具体的に書きなさい。詰まらなかった人は、6つの穴埋めのうち1つを選び、何という列名・メソッドを入れて、どんな結果が出たかを書きなさい。

目安:B1・B2あわせて300〜500字。自分の言葉で書くこと。

字数は目安です。多少の過不足で減点はしません。見るのは、設問が挙げた要素(上の箇条書き)が書けているかどうかです。ただし要素が抜けていれば、その分は引きます。

次回予告

第5回「相関と因果」。2つの列の関係を散布図と相関係数で見る。そして――「相関がある」ことと「因果がある」ことの、決定的な違いへ。