【初心者】研修の復習のため、AIにPythonの練習問題を作らせて解いてみた
はじめに
こんにちは。クラスメソッドオペレーションズの藤瀨です。
現在、外部研修でPythonを学習中で、ちょうどPythonでデータ分析を行う講座が終了しました。
初学者の私にとってはインプット量がかなり多く、定着しているか不安が残っていたため、AIに練習問題を作らせて復習することにしました。
このブログは難易度別の問題・解くためのヒント・実際に解いてみた私の解説とつまずいたポイントという構成になっています。
Pythonの基本とデータ分析の基礎的な内容(詳細は次のセクションのプロンプト参照)を学んだ方は解ける難易度になっていますので、私と同じ初心者の方はぜひ演習として活用してみてください。
解説は初学者目線で、私がしてしまったミスや悩んだポイントも合わせて書いています。
こちらは(ギリギリ?)人間ならではの内容かと思いますので、初心者の方はよろしければ参考にしてみてください!
問題
今回私はClaudeに問題を作ってもらいました。
AIを活用することで、自分のレベルに合わせた練習問題を手軽に作ることができ、ヒントや解説まで出力してくれるので自己学習においてとても役に立ちます。
プロンプトは以下の通りです。
プロンプトにはこのブログで扱うデータ分析のトピックも含まれています。
利用する際は自由に内容や難易度等を変えてください。
# Pythonデータ分析 練習問題生成プロンプト
あなたはPythonデータ分析の講師です。
以下の条件で練習問題セットを生成してください。
## 対象トピック
- pandas(DataFrame操作:読み込み・フィルタリング・集計・結合)
- NumPy(配列操作・統計計算)
- matplotlib/seaborn(データ可視化)
- データ前処理(欠損値処理・型変換・外れ値検出)
## 問題数・難易度
7問を以下のバランスで作成してください。
- ⭐(易):2問
- ⭐⭐(中):3問
- ⭐⭐⭐(難):2問
## データセット
- 全問題で共通して使える架空のデータセットを1つ用意し、コードで生成できるようにしてください。
- 外部ファイルに依存しないこと(pd.DataFrameで直接定義)。
## 出力形式
以下の3つを個別に生成してください。
### 1. 問題一覧
- 共通データセットの生成コード
- 各問題:番号・タイトル・難易度(⭐)・問題文
### 2. ヒント
- 各問題に対応するヒント
- 答えは直接与えず、使うべきメソッド名・考え方・注意点を記載
### 3. 解説・解答
- 各問題の完全な解答コード(コメント付き)
- コードの後に「なぜそう書くのか」の解説
## その他
- Python 3.10以降・pandas 2.x系で動作すること
- 日本語のカラム名・値を使うこと
本記事のコードを実行するには、以下のライブラリが必要です。
インストールしていない方は、ターミナルで以下を実行してください。
また、私はWindows OS環境で実行しています。
pip install pandas numpy matplotlib seaborn
各問題の下にヒントが隠れています。
実際に解いてみようという方で、ヒントを参照したい場合は左側のトグルをクリックして展開してください。
共通データセット
各問題では、以下の「ECサイト売上データ」を使用します。
まずはこのコードを実行してデータを準備してください。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
matplotlib.rcParams['font.family'] = 'MS Gothic'
# 売上データ
sales_data = {
'日付': [
'2024-01-07', '2024-01-14', '2024-01-21', '2024-01-28',
'2024-02-04', '2024-02-11', '2024-02-18', '2024-02-25',
'2024-03-03', '2024-03-10', '2024-03-17', '2024-03-24',
'2024-04-07', '2024-04-14', '2024-04-21', '2024-04-28',
'2024-05-05', '2024-05-12', '2024-05-19', '2024-05-26',
],
'商品名': [
'ノートPC', 'スマートフォン', 'タブレット', 'イヤホン',
'ノートPC', 'マウス', 'スマートフォン', 'タブレット',
'イヤホン', 'ノートPC', 'マウス', 'スマートフォン',
'タブレット', 'ノートPC', 'イヤホン', 'マウス',
'スマートフォン', 'ノートPC', 'タブレット', 'イヤホン',
],
'カテゴリ': [
'PC', 'スマートフォン', 'タブレット', 'アクセサリ',
'PC', 'アクセサリ', 'スマートフォン', 'タブレット',
'アクセサリ', 'PC', 'アクセサリ', 'スマートフォン',
'タブレット', 'PC', 'アクセサリ', 'アクセサリ',
'スマートフォン', 'PC', 'タブレット', 'アクセサリ',
],
'地域': [
'東京', '大阪', '東京', '名古屋',
'大阪', '東京', '東京', '名古屋',
'大阪', '東京', '名古屋', '大阪',
'東京', '大阪', '東京', '名古屋',
'東京', '大阪', '名古屋', '東京',
],
'単価': [
98000, 65000, 45000, 8500,
102000, 3200, 71000, 48000,
9200, 89000, 2900, 58000,
52000, 115000, 7800, 3500,
68000, 108000, 43000, 8100,
],
'販売数': [
2, 3, 5, 10,
3, 8, 2, 4,
12, 2, 7, 4,
3, 3, 11, 9,
2, 2, 5, 9,
],
}
df = pd.DataFrame(sales_data)
df['日付'] = pd.to_datetime(df['日付'])
難易度⭐
問題1:DataFrameの基本確認 テーマ:pandas入門
上で作成した df を使って、以下の情報を確認してください。
- 先頭5行を表示してください。
- データの行数・列数を確認してください。
- 各列のデータ型を確認してください。
- 数値列の基本統計量(平均・最大・最小・標準偏差など)を一度に表示してください。
カテゴリ列に何種類の値があるか調べ、それぞれの件数も表示してください。
ヒント
| やること | 使うもの |
|---|---|
| 先頭N行を見る | df.head(N) |
| 行数・列数を確認 | df.shape → (行数, 列数) のタプルで返ってくる |
| 各列のデータ型 | df.dtypes |
| 数値列の基本統計量 | df.describe() |
| 列のユニーク値の種類数 | df['列名'].nunique() |
| 列の値ごとの件数 | df['列名'].value_counts() |
💡
describe()はデフォルトで数値型の列だけを集計します。文字列列も含めたい場合はinclude='all'を指定しましょう。
問題2:NumPy配列の操作と統計計算 テーマ:NumPy基礎
df['単価'] の値を使って、NumPyで以下の計算を行ってください。
df['単価']をNumPy配列に変換してください。- 以下の値を計算して表示してください。
- 合計 / 平均 / 中央値 / 標準偏差 / 最大値 / 最小値
- 単価が50,000円以上のデータだけを抽出してください(NumPyのブールインデックスを使用)。
- 配列を4行5列の2次元配列に変換してください。
- 変換した2次元配列の各行の合計を計算してください。
ヒント
| やること | 使うもの |
|---|---|
| pandas Series → NumPy配列 | .values または .to_numpy() |
| 合計 | np.sum(arr) |
| 平均 | np.mean(arr) |
| 中央値 | np.median(arr) |
| 標準偏差 | np.std(arr) |
| 最大 / 最小 | np.max(arr) / np.min(arr) |
| ブールインデックスで抽出 | arr[arr > 50000] のように書く |
| 配列の形を変える | arr.reshape(行, 列) |
| 各行の合計(2次元配列) | arr2d.sum(axis=1) |
💡
reshapeでは元の要素数と変換後の要素数が一致している必要があります。今回は20個の要素を4×5に変換します。
難易度⭐⭐
問題3:データのフィルタリングと並び替え テーマ:pandas フィルタリング
単価が 50,000円を超える 行だけを抽出してください。地域が 「東京」 の行だけを抽出してください。地域が「東京」 かつカテゴリが「PC」の行を抽出してください。単価の高い順に並び替えて、上位5件を表示してください。locを使って、単価が 10,000円未満の行の商品名と単価の2列だけを取り出してください。
ヒント
| やること | 使うもの |
|---|---|
| 数値条件でフィルタ | df[df['列名'] > 値] |
| 文字列一致でフィルタ | df[df['列名'] == '値'] |
| AND条件 | df[(条件1) & (条件2)] ← 括弧が必要! |
| 並び替え(降順) | df.sort_values('列名', ascending=False) |
| 先頭N件 | .head(N) |
| 行・列を指定して取得 | df.loc[行条件, ['列名1', '列名2']] |
💡 複数条件を組み合わせるときは 各条件を
()で囲む ことを忘れずに。andではなく&、orではなく|を使います。
問題4:GroupByによる集計分析 テーマ:pandas GroupBy
カテゴリごとの単価の合計・平均・件数を一度に集計してください。地域ごとの販売数の合計を求め、多い順に並び替えて表示してください。カテゴリと地域の2軸でグループ化し、単価の合計を集計してください。- 各
カテゴリで最も単価が高かった行(商品名・地域・単価)を取り出してください。
ヒント
| やること | 使うもの |
|---|---|
| グループ化して集計 | df.groupby('列名')['対象列'].集計関数() |
| 複数の集計を一度に | .agg(['sum', 'mean', 'count']) |
| 複数列でグループ化 | df.groupby(['列1', '列2']) |
| 並び替え | .sort_values(ascending=False) |
| 各グループの最大値の行を取得 | df.loc[df.groupby('列名')['値列'].idxmax()] |
💡
agg()には辞書形式{'列名': ['sum', 'mean']}で渡すと、どの列にどの集計を適用するか細かく指定できます。
💡
idxmax()はグループ内で最大値を持つ行のインデックス番号を返します。それをdf.loc[]に渡すと、その行全体を取得できます。
問題5:欠損値の検出と処理 テーマ:データ前処理
以下の欠損値を含むデータを使ってください(共通データセットとは別)。
messy_data = {
'商品名': ['ノートPC', 'スマートフォン', 'タブレット', 'イヤホン', 'マウス',
'ノートPC', 'スマートフォン', 'タブレット', 'イヤホン', 'マウス'],
'単価': [98000, None, 45000, 8500, None,
102000, 65000, None, 9200, 3200],
'販売数': [2, 3, None, 10, 8,
None, 2, 4, None, 7],
'評価スコア': [4.5, 3.8, 4.2, None, 3.5,
4.7, None, 4.0, 3.9, None],
}
df_messy = pd.DataFrame(messy_data)
- 各列の欠損値の数を確認してください。
- データ全体で欠損値が含まれる行数を求めてください。
単価の欠損値をその列の平均値で埋めてください。販売数の欠損値を中央値で埋めてください。評価スコアに欠損値がある行を削除してください(元のDataFrameは変えず、新しい変数に代入)。
ヒント
| やること | 使うもの |
|---|---|
| 欠損値かどうかを確認 | df.isnull() → True/FalseのDataFrame |
| 列ごとの欠損値の数 | df.isnull().sum() |
| 欠損値を含む行数 | df.isnull().any(axis=1).sum() |
| 欠損値を特定の値で埋める | df['列名'].fillna(値) |
| 平均値で埋める | df['列名'].fillna(df['列名'].mean()) |
| 中央値で埋める | df['列名'].fillna(df['列名'].median()) |
| 欠損値のある行を削除 | df.dropna(subset=['列名']) |
💡
fillna()やdropna()は元のDataFrameを変更しません。結果を変数に代入するか、inplace=Trueを使いましょう。
💡 特定の列に欠損値がある行だけを削除したいときは
subset=['列名']を指定します。
難易度⭐⭐⭐
問題6:データの可視化 テーマ:matplotlib/seaborn
共通データセット df を使って、以下のグラフを作成してください。
-
棒グラフ:
カテゴリごとの単価の合計を棒グラフで表示してください。
(タイトル・軸ラベルも設定すること) -
折れ線グラフ:
日付ごとの単価を折れ線グラフで表示してください。
(x軸の日付が読みやすくなるよう回転させること) -
箱ひげ図:seabornを使って、
地域ごとの単価の分布を箱ひげ図で表示してください。 -
相関ヒートマップ:数値列(
単価・販売数)の相関行列を計算し、seabornのヒートマップで可視化してください。
ヒント
棒グラフ(matplotlib)
# groupbyで集計してから.plot.bar()またはplt.bar()を使う
grouped = df.groupby('カテゴリ')['単価'].sum()
grouped.plot.bar()
plt.title('タイトル')
plt.xlabel('x軸ラベル')
plt.ylabel('y軸ラベル')
plt.tight_layout()
plt.show()
折れ線グラフ(matplotlib)
# x軸の日付ラベルを回転させるには
plt.xticks(rotation=45)
# または
fig.autofmt_xdate()
箱ひげ図(seaborn)
#xに地域、yに単価を指定
sns.boxplot(data=df, x='地域', y='単価')
相関ヒートマップ(seaborn)
# 相関行列の計算
corr = df[['列1', '列2']].corr()
# ヒートマップ
sns.heatmap(corr, annot=True, fmt='.2f')
💡
annot=Trueを指定すると相関係数の数値がセルの中に表示されます。
💡 グラフを複数作る場合は
plt.figure()で新しいキャンバスを作るとグラフが重なりません。
問題7:複数DataFrameの結合と総合分析 テーマ:データ結合・総合
以下の「商品マスタ」を追加で用意してください。
product_master = pd.DataFrame({
'商品名': ['ノートPC', 'スマートフォン', 'タブレット', 'イヤホン', 'マウス'],
'原価': [60000, 35000, 25000, 3000, 1200],
'ブランド': ['TechPro', 'SmartCo', 'TechPro', 'SoundX', 'PeriCo'],
})
dfとproduct_masterを商品名をキーに**内部結合(inner join)**してください。- 結合後のDataFrameに
利益列を追加してください。
(利益 = (単価 - 原価) × 販売数) ブランドごとの利益の合計を集計し、多い順に表示してください。利益率(利益 ÷ (単価 × 販売数) * 100)を列として追加し、利益率が30%以上の行だけを抽出してください。カテゴリと地域でグループ化して利益の平均を集計し、ピボットテーブル形式(カテゴリを行、地域を列)で表示してください。
ヒント
| やること | 使うもの |
|---|---|
| 内部結合 | pd.merge(df1, df2, on='キー列', how='inner') |
| 左結合 | how='left' |
| 新しい列の追加(計算式) | df['新列名'] = df['列1'] - df['列2'] * df['列3'] |
| ピボットテーブル | df.pivot_table(values='値列', index='行', columns='列', aggfunc='mean') |
💡
pd.merge()のhow='inner'は両方のDataFrameに存在するキーのみ残します。今回はすべての商品名がマスタに存在するので結果は同じ行数になるはずです。
💡
利益率の計算では0除算が起きないよう注意しましょう(今回のデータでは売上金額は0以上なので問題ありませんが)。
💡
pivot_table()のaggfuncには'sum','mean','count'などを指定できます。
解説とつまずきポイント
問題ごとの解答例と解説、それから私が解いたときにミスしたりエラーになったりした点があれば合わせて記載します。
問題1
解答
# 1. 先頭5行を表示
print(df.head())
# 2. 行数・列数
print(df.shape) # → (20, 6)
print(f"行数: {df.shape[0]}, 列数: {df.shape[1]}")
# 3. 各列のデータ型
print(df.dtypes)
# 4. 基本統計量
print(df.describe())
# 5. カテゴリ列のユニーク種類数と件数
print(f"カテゴリの種類数: {df['カテゴリ'].nunique()}")
print(df['カテゴリ'].value_counts())
解説
-
df.head()はデータの先頭5行(デフォルト)を表示します。head(10)のように引数を指定すると任意の行数を表示できます。 -
df.shapeはタプル(行数, 列数)を返します。df.shape[0]で行数、df.shape[1]で列数を単独で取得できます。 -
df.dtypesは各列のデータ型を返します。objectは文字列、int64やfloat64は数値、datetime64は日付時刻を表します。 -
df.describe()は数値列に対してcount・mean・std・min・25%・50%・75%・maxの8種類の統計量をまとめて表示します。データの大まかな分布や外れ値の有無をざっくり確認するのに便利です。 -
value_counts()は各値が何回登場するかを、多い順に返します。カテゴリ列の偏りを確認するときによく使います。
問題2
解答
# 1. NumPy配列に変換
arr = df['単価'].values # または.to_numpy()
print(type(arr)) # → <class 'numpy.ndarray'>
# 2. 基本統計量を計算
print(f"合計 : {np.sum(arr):,}")
print(f"平均 : {np.mean(arr):,.1f}")
print(f"中央値 : {np.median(arr):,}")
print(f"標準偏差: {np.std(arr):,.1f}")
print(f"最大値 : {np.max(arr):,}")
print(f"最小値 : {np.min(arr):,}")
# 3. 50,000円以上をブールインデックスで抽出
mask = arr >= 50000
print(arr[mask])
# 4. 4行5列の2次元配列に変換
arr_2d = arr.reshape(4, 5)
print(arr_2d)
print(f"形状: {arr_2d.shape}") # → (4, 5)
# 5. 各行の合計
row_sums = arr_2d.sum(axis=1)
print(row_sums)
解説
-
.valuesまたは.to_numpy()で pandas のSeriesをNumPyのndarrayに変換できます。 -
ブールインデックスはNumPyの強力な機能です。
arr >= 50000とすると各要素が条件を満たすかのTrue/False配列が返ります。それをarr[...]に渡すと、Trueの要素だけが抽出されます。 -
reshape(4, 5)は1次元の20要素の配列を「4行5列」の2次元配列に変換します。4×5=20なので要素数が一致しています。 -
sum(axis=1)のaxisは「どの方向に集計するか」を指定します。axis=0は列方向(縦)に集計し、axis=1は行方向(横)に集計します。
問題3
解答
# 1. 単価が50,000円を超える行
df_high = df[df['単価'] > 50000]
print(df_high)
# 2. 地域が東京の行
df_tokyo = df[df['地域'] == '東京']
print(df_tokyo)
# 3. 東京 かつ カテゴリがPC
df_tokyo_pc = df[(df['地域'] == '東京') & (df['カテゴリ'] == 'PC')]
print(df_tokyo_pc)
# 4. 単価の高い順に並び替えて上位5件
df_top5 = df.sort_values('単価', ascending=False).head(5)
print(df_top5)
# 5. locで単価が10,000円未満の行の商品名と単価
df_low = df.loc[df['単価'] < 10000, ['商品名', '単価']]
print(df_low)
解説
1, 2. ブールフィルタ df[条件] は条件が True の行だけを返します。これはpandasで最も頻繁に使うフィルタリングの書き方です。
-
AND条件を書くときは
&を使い、必ず各条件を()で囲みます。andは使えません。同様にORは|、NOTは~です。 -
sort_values()はデフォルトで昇順(ascending=True)です。降順にするにはascending=Falseを指定します。 -
df.loc[行条件, 列リスト]は「行の条件」と「取得する列」を同時に指定できる書き方です。ilocは番号(整数インデックス)で指定するのに対し、locはラベル(列名や条件式)で指定します。
問題4
解答
# 1. カテゴリごとに単価の合計・平均・件数を集計
result1 = df.groupby('カテゴリ')['単価'].agg(['sum', 'mean', 'count'])
result1.columns = ['合計', '平均', '件数'] # 列名を日本語に変更
print(result1)
# 2. 地域ごとの販売数の合計(多い順)
result2 = (df.groupby('地域')['販売数']
.sum()
.sort_values(ascending=False))
print(result2)
# 3. カテゴリ×地域の2軸集計
result3 = df.groupby(['カテゴリ', '地域'])['単価'].sum()
print(result3)
# 4. カテゴリごとに単価が最大の行を取得
idx = df.groupby('カテゴリ')['単価'].idxmax()
result4 = df.loc[idx, ['カテゴリ', '商品名', '地域', '単価']]
print(result4)
解説
-
groupby()は指定した列の値でデータをグループに分け、その後に続く集計関数を各グループに適用します。 -
agg()に関数名のリストを渡すと、複数の集計をまとめて計算できます。辞書形式{'単価': ['sum', 'mean']}を渡すと列ごとに集計関数を変えることもできます。 -
複数列でのグループ化
groupby(['カテゴリ', '地域'])のようにリストで複数列を指定します。結果はマルチインデックスになります。 -
idxmax()はグループ内で最大値をとる行のインデックス番号を返します。それをdf.loc[idx]に渡すことで、最大値を持つ行全体を取得できます。
問題5
解答
messy_data = {
'商品名': ['ノートPC', 'スマートフォン', 'タブレット', 'イヤホン', 'マウス',
'ノートPC', 'スマートフォン', 'タブレット', 'イヤホン', 'マウス'],
'単価': [98000, None, 45000, 8500, None,
102000, 65000, None, 9200, 3200],
'販売数': [2, 3, None, 10, 8,
None, 2, 4, None, 7],
'評価スコア': [4.5, 3.8, 4.2, None, 3.5,
4.7, None, 4.0, 3.9, None],
}
df_messy = pd.DataFrame(messy_data)
# 1. 各列の欠損値の数
print(df_messy.isnull().sum())
# 2. 欠損値を含む行数
missing_rows = df_messy.isnull().any(axis=1).sum()
print(f"欠損値を含む行数: {missing_rows}")
# 3. 単価の欠損値を平均で埋める(元のDFを更新)
mean_sales = df_messy['単価'].mean()
df_messy['単価'] = df_messy['単価'].fillna(mean_sales)
print(f"単価の平均: {mean_sales:,.1f}")
print(df_messy[['商品名', '単価']])
# 4. 販売数の欠損値を中央値で埋める
median_qty = df_messy['販売数'].median()
df_messy['販売数'] = df_messy['販売数'].fillna(median_qty)
print(f"販売数の中央値: {median_qty}")
print(df_messy[['商品名', '販売数']])
# 5. 評価スコアに欠損値がある行を削除
df_clean = df_messy.dropna(subset=['評価スコア'])
print(f"削除前: {len(df_messy)}行 → 削除後: {len(df_clean)}行")
print(df_clean)
解説
-
isnull()は欠損値(NoneやNaN)の場所をTrueにしたDataFrameを返します。それに.sum()を適用すると列ごとの欠損値の数がわかります。 -
any(axis=1)は「各行にTrueが1つでも含まれるか」を判定します。これで「どこかに欠損値がある行」を特定できます。
3, 4. 欠損値の補完方法の選び方:
- 平均値(mean):数値が正規分布に近い場合に向いています
- 中央値(median):外れ値の影響を受けにくいため、分布が偏っている場合に向いています
- 最頻値(mode):カテゴリ変数(文字列)の補完に使います
dropna(subset=['列名'])は指定した列に欠損がある行だけを削除します。subsetを省略するとすべての列をチェックして、どこかに欠損値がある行をすべて削除します。
問題6
解答
# フォント設定(日本語を表示するため)
import matplotlib
matplotlib.rcParams['font.family'] = 'MS Gothic' # Windowsの場合。'Yu Gothic'でも可
# Macの場合: 'Hiragino Sans' または 'AppleGothic'
# Linuxの場合: 'IPAexGothic'
# 1. 棒グラフ:カテゴリごとの単価合計
plt.figure(figsize=(8, 5))
category_sales = df.groupby('カテゴリ')['単価'].sum().sort_values(ascending=False)
plt.bar(category_sales.index, category_sales.values, color='steelblue')
plt.title('カテゴリ別 単価合計', fontsize=14)
plt.xlabel('カテゴリ', fontsize=12)
plt.ylabel('単価(円)', fontsize=12)
plt.tight_layout()
plt.show()
# 2. 折れ線グラフ:日付ごとの単価
plt.figure(figsize=(10, 5))
plt.plot(df['日付'], df['単価'], marker='o', color='tomato', linewidth=1.5)
plt.title('週次 単価推移', fontsize=14)
plt.xlabel('日付', fontsize=12)
plt.ylabel('単価(円)', fontsize=12)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# 3. 箱ひげ図:地域ごとの単価分布
plt.figure(figsize=(8, 5))
sns.boxplot(data=df, x='地域', y='単価', palette='Set2')
plt.title('地域別 単価の分布', fontsize=14)
plt.xlabel('地域', fontsize=12)
plt.ylabel('単価(円)', fontsize=12)
plt.tight_layout()
plt.show()
# 4. 相関ヒートマップ
plt.figure(figsize=(5, 4))
corr = df[['単価', '販売数']].corr()
sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', vmin=-1, vmax=1)
plt.title('相関行列ヒートマップ', fontsize=14)
plt.tight_layout()
plt.show()
解説
日本語フォントの設定は環境によって異なります。文字化けが発生した場合は matplotlib.rcParams['font.family'] に適切なフォント名を指定してください。japanize-matplotlib というライブラリを使うと import japanize_matplotlib だけで自動設定できます。
figsize=(幅, 高さ) でグラフのサイズをインチ単位で指定します。
plt.tight_layout() はサブプロットのパディングを自動調整し、ラベルが切れるのを防ぎます。
別解:4つのグラフを1つの画面にまとめて表示する
問題6では4つのグラフを別々のウィンドウで表示しましたが、plt.subplot() を使うと1つの画面にまとめて並べることもできます。
plt.figure(figsize=(14, 10))
# 1. 棒グラフ(左上)
plt.subplot(2, 2, 1)
category_sales = df.groupby('カテゴリ')['単価'].sum().sort_values(ascending=False)
plt.bar(category_sales.index, category_sales.values, color='steelblue')
plt.title('カテゴリ別 単価合計')
plt.xlabel('カテゴリ')
plt.ylabel('単価(円)')
# 2. 折れ線グラフ(右上)
plt.subplot(2, 2, 2)
plt.plot(df['日付'], df['単価'], marker='o', color='tomato', linewidth=1.5)
plt.title('週次 単価推移')
plt.xticks(rotation=45)
# 3. 箱ひげ図(左下)
plt.subplot(2, 2, 3)
sns.boxplot(data=df, x='地域', y='単価', palette='Set2')
plt.title('地域別 単価の分布')
# 4. 相関ヒートマップ(右下)
plt.subplot(2, 2, 4)
corr = df[['単価', '販売数']].corr()
sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', vmin=-1, vmax=1)
plt.title('相関行列ヒートマップ')
plt.tight_layout()
plt.show()
plt.subplot(2, 2, 1) の3桁は「2行2列のグリッドの1番目」という意味で、左上から右に向かって1・2・3・4と番号が振られます。各 plt.subplot() の後に続けてグラフを描くことで、その位置にグラフが配置されます。
問題7
解答
# 商品マスタ
product_master = pd.DataFrame({
'商品名': ['ノートPC', 'スマートフォン', 'タブレット', 'イヤホン', 'マウス'],
'原価': [60000, 35000, 25000, 3000, 1200],
'ブランド': ['TechPro', 'SmartCo', 'TechPro', 'SoundX', 'PeriCo'],
})
# 1. 内部結合
df_merged = pd.merge(df, product_master, on='商品名', how='inner')
print(f"結合後の行数: {len(df_merged)}")
print(df_merged.head())
# 2. 利益列を追加(単価に販売数を掛けて合計売上を求める)
df_merged['利益'] = (df_merged['単価'] - df_merged['原価']) * df_merged['販売数']
print(df_merged[['商品名', '単価', '原価', '販売数', '利益']].head(5))
# 3. ブランドごとの利益合計(多い順)
brand_profit = (df_merged.groupby('ブランド')['利益']
.sum()
.sort_values(ascending=False))
print(brand_profit)
# 4. 利益率の追加と絞り込み(合計売上 = 単価 × 販売数)
df_merged['利益率'] = df_merged['利益'] / (df_merged['単価'] * df_merged['販売数']) * 100
df_high_margin = df_merged[df_merged['利益率'] >= 30]
print(df_high_margin[['商品名', '地域', '単価', '利益', '利益率']])
# 5. ピボットテーブル(カテゴリ×地域の利益平均)
pivot = df_merged.pivot_table(
values='利益',
index='カテゴリ',
columns='地域',
aggfunc='mean'
)
print(pivot.round(0)) # 小数点以下を丸める
解説
pd.merge()はSQLのJOINに相当する操作です。on='商品名'でキーとなる列を指定します。howパラメータによって結合方法が変わります:
| how の値 | 動作 |
|---|---|
'inner' |
両方のDataFrameに存在するキーのみ残す |
'left' |
左(第1引数)のDataFrameのキーをすべて残す |
'right' |
右(第2引数)のDataFrameのキーをすべて残す |
'outer' |
両方のすべてのキーを残す(欠損値が生じる場合あり) |
-
利益の計算式 単価・原価はともに商品1個あたりの価格なので、差し引いた利益に
販売数を掛けます。 -
groupby('ブランド')['利益'].sum()はブランドごとに利益を合計します。問題4で学んだgroupbyの応用です。結合後のDataFrameにはブランド列が追加されているので、それをキーに集計できます。.sort_values(ascending=False)を続けてチェーンすることで、集計と並び替えを1行にまとめて書けます。 -
利益率の計算式 分母の
単価 × 販売数は「合計売上金額」を表します。利益率30%以上の絞り込みは問題3で学んだブールフィルタの応用です。 -
pivot_table()は「カテゴリ(行)×地域(列)」のクロス集計表を作れる関数です。
# pivot_tableのパラメータの意味
pivot_table(
values='利益', # 集計する値の列
index='カテゴリ', # 行(縦軸)にするカテゴリ
columns='地域', # 列(横軸)にするカテゴリ
aggfunc='mean' # 集計方法(今回は平均)
)
データが存在しない組み合わせ(例:あるカテゴリが特定地域で売れていない場合)は NaN になります。fill_value=0 を追加すると 0 で埋めることができます。
まとめ
一通り問題を解いてみて、自分の苦手な内容や復習しなければならない内容を明確にすることができました。
AIに壁打ちしながら学習に取り組むことで、疑問の解消だけでなくより深い知識の定着につながると感じます。
この記事が私と同じようにPythonを勉強中の方の役に立てれば幸いです。
最後までお読みいただきありがとうございました!
付録:よく使うメソッド早見表
| 操作 | コード例 |
|---|---|
| 先頭N行表示 | df.head(N) |
| 基本統計量 | df.describe() |
| 欠損値の数 | df.isnull().sum() |
| 欠損値補完 | df['列'].fillna(値) |
| フィルタリング | df[df['列'] > 値] |
| AND条件 | df[(条件A) & (条件B)] |
| 並び替え | df.sort_values('列', ascending=False) |
| グループ集計 | df.groupby('列')['値'].agg(['sum', 'mean']) |
| DataFrame結合 | pd.merge(df1, df2, on='列', how='inner') |
| ピボットテーブル | df.pivot_table(values, index, columns, aggfunc) |
| 棒グラフ | plt.bar(x, y) |
| 箱ひげ図 | sns.boxplot(data=df, x='列', y='列') |
| ヒートマップ | sns.heatmap(corr, annot=True) |
クラスメソッドオペレーションズ株式会社について
クラスメソッドグループのオペレーション企業です。
運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AIをフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイトをぜひご覧ください。
※2026年1月 アノテーション㈱から社名変更しました。








