Python入門とデータ処理の基礎
第2回 – NumPyとPandasによるデータ操作

Python入門とデータ処理の基礎第2回 – NumPyとPandasによるデータ操作

前回は、Pythonの基本的な文法を学びました。

変数の使い方、条件分岐、繰り返し処理、そして関数の作り方など、プログラミングの基礎となる重要な概念を身につけることができました。

Python入門とデータ処理の基礎第1回 – Pythonの基礎文法

しかし、実際のデータ分析の現場では、数万件、数十万件といった大量のデータを扱うことが珍しくありません。

前回学んだリストや辞書だけでこのような大規模データを処理しようとすると、処理速度が遅く、メモリも大量に消費してしまいます。

そこで今回は、データサイエンスの世界で欠かせない2つの強力なライブラリ、NumPyとPandasを学びます。

NumPyは高速な数値計算を可能にし、Pandasは表形式のデータを直感的に操作できる機能を提供してくれます。

この2つのライブラリをマスターすることで、実際のビジネスデータを効率的に処理し、有意義な分析を行えるようになります。

ExcelやGoogleスプレッドシートでは処理しきれないような大きなデータでも、Pythonなら楽々と扱うことができるのです。

NumPy入門:高速な数値計算の世界

 NumPyとは何か、なぜ必要なのか

NumPyは「Numerical Python」の略で、数値計算を高速に行うためのライブラリです。

「高速」というのがポイントで、通常のPythonリストと比べて、10倍から100倍以上速く計算できることもあります。

なぜそんなに速いのでしょうか。

実は、NumPyの中身はC言語という、より機械に近い言語で書かれています。

Pythonは人間にとって書きやすい言語ですが、その分コンピュータにとっては処理に時間がかかります。

NumPyは、重い計算部分をC言語で実装することで、Pythonの書きやすさと、C言語の速さの両方の利点を享受できるようにしているのです。

まず、NumPyをインポート(読み込む)ところから始めましょう。

# NumPyをインポートする
# 慣習的に np という短い名前をつけます
import numpy as np

# バージョンを確認
print(f"NumPyのバージョン: {np.__version__}")

 

以下、実行結果です。人によってバージョンは異なります。

NumPyのバージョン: 1.25.2

 

次に、通常のPythonリストとNumPy配列の違いを体感してみましょう。

見た目はほとんど同じですが、実は内部的には全く異なる構造をしています。

# 通常のPythonリスト
python_list = [1, 2, 3, 4, 5]
print("Pythonリスト:", python_list)
print("型:", type(python_list))

 

以下、実行結果です。

Pythonリスト: [1, 2, 3, 4, 5]
型: <class 'list'>

 

同じデータをNumPy配列として作成してみます。

# NumPy配列に変換
numpy_array = np.array([1, 2, 3, 4, 5])
print("NumPy配列:", numpy_array)
print("型:", type(numpy_array))

 

以下、実行結果です。

NumPy配列: [1 2 3 4 5]
型: <class 'numpy.ndarray'>

 

見た目はほとんど同じですが、NumPy配列には重要な特徴があります。

すべての要素が同じデータ型でなければならないのです。

これにより、メモリを効率的に使い、高速な計算が可能になります。

 

 NumPy配列の作成方法いろいろ

NumPy配列を作る方法はたくさんあります。

それぞれの方法と、どんなときに使うのかを見ていきましょう。

最も基本的なのは、リストから配列を作る方法です。

# リストから配列を作る(最も基本的な方法)
scores = np.array([85, 90, 78, 92, 88])
print("テストの点数:", scores)

 

以下、実行結果です。

テストの点数: [85 90 78 92 88]

 

データ分析では、初期値を設定した配列(特定の値だけの配列)を作ることがよくあります。

例えば、まだデータが入力されていない状態を表現するために、すべて0の配列を作ります。

# すべて0の配列を作る(初期化でよく使う)
zeros = np.zeros(5)  # 5個の0を持つ配列
print("ゼロ配列:", zeros)

 

以下、実行結果です。

ゼロ配列: [0. 0. 0. 0. 0.]

 

同様に、すべて1の配列や、特定の値で埋められた配列も作れます。

# すべて1の配列を作る
ones = np.ones(5)
print("1の配列:", ones)

 

以下、実行結果です。

1の配列: [1. 1. 1. 1. 1.]

 

連続した数値の配列を作ることも多いです。

Pythonのrange()関数のNumPy版としてarange()があります

# 連続した数値の配列を作る(rangeのNumPy版)
# 0から9まで
sequence = np.arange(10)
print("0から9:", sequence)

# 1から10まで
sequence2 = np.arange(1, 11)
print("1から10:", sequence2)

# 2ずつ増える数列
even_numbers = np.arange(0, 11, 2)
print("偶数:", even_numbers)

 

以下、実行結果です。

0から9: [0 1 2 3 4 5 6 7 8 9]
1から10: [ 1  2  3  4  5  6  7  8  9 10]
偶数: [ 0  2  4  6  8 10]

 

グラフを描くときなどに便利な、等間隔の数値を生成するlinspace()という関数もあります

# 等間隔の数値を生成(グラフの軸などで使う)
# 0から10までを5等分
linear_space = np.linspace(0, 10, 5)
print("0から10を5等分:", linear_space)
# 注意:linspaceは終了値も含みます(arangeは含まない)

 

以下、実行結果です。

0から10を5等分: [ 0.   2.5  5.   7.5 10. ]

 

 配列の要素にアクセスする

NumPy配列の要素にアクセスする方法は、リストと同じように0から始まるインデックスを指定しアクセスします。

# 配列の要素へのアクセス
sales = np.array([320, 285, 410, 355, 298])
print("売上データ(日単位):", sales)

# インデックスでアクセス(0から始まる)
print(f"1日目の売上: {sales[0]}万円")
print(f"3日目の売上: {sales[2]}万円")
print(f"最後の日の売上: {sales[-1]}万円")

 

以下、実行結果です。

売上データ(日単位): [320 285 410 355 298]
1日目の売上: 320万円
3日目の売上: 410万円
最後の日の売上: 298万円

 

配列の一部を取り出す「スライシング」も、リストと同じように使えます。

# スライシング(一部を取り出す)
print(f"2日目から4日目: {sales[1:4]}")  # インデックス1から3まで
print(f"最初の3日間: {sales[:3]}")
print(f"最後の2日間: {sales[-2:]}")

 

以下、実行結果です。

2日目から4日目: [285 410 355]
最初の3日間: [320 285 410]
最後の2日間: [355 298]

 

配列の要素を変更することもできます。

# 配列の要素を変更する
print("売上データを修正")
print("修正前:", sales)
sales[1] = 300  # 2日目の売上を修正
print("修正後:", sales)

# 複数の要素を一度に変更
sales[2:4] = [420, 360]  # 3日目と4日目を修正
print("複数修正後:", sales)

 

以下、実行結果です。

売上データを修正
修正前: [320 285 410 355 298]
修正後: [320 300 410 355 298]
複数修正後: [320 300 420 360 298]

 

 NumPyの真骨頂:ベクトル化演算

ここからがNumPyの本当の力を発揮する部分です。

「ベクトル化演算」という機能により、配列全体に対して一度に演算を適用できます。

まず、通常のPythonリストで税込価格を計算する場合を見てみましょう。

ループを使って一つずつ計算する必要があります。

# 通常のPythonリストでの計算(面倒な方法)
python_prices = [1000, 1500, 2000, 1200, 1800]

python_tax_included = []

for price in python_prices:
    python_tax_included.append(price * 1.1)  # 10%の税を加算

    print("Pythonリストでの税込価格:", python_tax_included)

 

以下、実行結果です。

Pythonリストでの税込価格: [1100.0, 1650.0000000000002, 2200.0, 1320.0, 1980.0000000000002]

 

NumPyなら、この計算が驚くほど簡単になります。

# NumPyでの計算(簡潔で高速)
numpy_prices = np.array([1000, 1500, 2000, 1200, 1800])

numpy_tax_included = numpy_prices * 1.1  # 全要素に一度に1.1を掛ける

print("NumPyでの税込価格:", numpy_tax_included)

 

以下、実行結果です。

NumPyでの税込価格: [1100. 1650. 2200. 1320. 1980.]

 

たった一行で、すべての要素に同じ計算を適用できました。これがベクトル化演算の威力です。より複雑な計算も簡単に書けます:

# さらに複雑な計算も簡単に
base_salary = np.array([300000, 350000, 400000, 280000, 320000])  # 基本給(円)
print("基本給:", base_salary)

# ボーナスは基本給の2.5ヶ月分
bonus = base_salary * 2.5
print("ボーナス:", bonus)

# 年収を計算(基本給×12 + ボーナス)
annual_income = base_salary * 12 + bonus
print("年収:", annual_income)

# 税金を20%として、手取り年収を計算
take_home = annual_income * 0.8
print("手取り年収:", take_home)

 

以下、実行結果です。

基本給: [300000 350000 400000 280000 320000]
ボーナス: [ 750000.  875000. 1000000.  700000.  800000.]
年収: [4350000. 5075000. 5800000. 4060000. 4640000.]
手取り年収: [3480000. 4060000. 4640000. 3248000. 3712000.]

 

配列同士の演算も、同じように簡単に行えます。

# 配列同士の演算
january_sales = np.array([100, 120, 90, 110, 105])  # 1月の売上
february_sales = np.array([110, 115, 95, 120, 110])  # 2月の売上

# 成長率を計算(2月 / 1月)
growth_rate = february_sales / january_sales
print("成長率:", growth_rate)
print("成長率(%表示):", (growth_rate - 1) * 100)

 

以下、実行結果です。

成長率: [1.1        0.95833333 1.05555556 1.09090909 1.04761905]
成長率(%表示): [10.         -4.16666667  5.55555556  9.09090909  4.76190476]

 

売上の増減を分析してみましょう。

# 売上の差を計算
difference = february_sales - january_sales
print("売上増減:", difference)

# 条件を使った処理
# 売上が増えた店舗だけを抽出
increased = difference > 0  # 各要素について True/False の配列を作る
print("売上増の店舗(True/False):", increased)

 

以下、実行結果です。

売上増減: [10 -5  5 10  5]
売上増の店舗(True/False): [ True False  True  True  True]

 

このTrue/Falseの配列を使って、条件に合う要素だけを取り出すこともできます。

# True の位置の店舗番号を取得
store_numbers = np.arange(1, 6)  # 店舗番号(1から5)
improved_stores = store_numbers[increased]  # Trueの店舗を抽出

print("売上が増えた店舗番号:", improved_stores)

 

以下、実行結果です。

売上が増えた店舗番号: [1 3 4 5]

 

 統計関数:データの特徴を一瞬で把握

NumPyには、データの統計的な特徴を計算する関数が豊富に用意されています。まず、サンプルデータを用意しましょう。

# サンプルデータ:ある商品の10日間の売上個数
daily_sales = np.array([45, 52, 48, 55, 42, 58, 51, 49, 53, 47])

print("日別売上個数:", daily_sales)

 

以下、実行結果です。

日別売上個数: [45 52 48 55 42 58 51 49 53 47]

 

基本的な統計量を計算してみます。

# 基本統計量を一気に計算
print(f"合計: {np.sum(daily_sales)}個")
print(f"平均: {np.mean(daily_sales):.1f}個")
print(f"中央値: {np.median(daily_sales)}個")
print(f"標準偏差: {np.std(daily_sales):.2f}")  # ばらつきの指標
print(f"分散: {np.var(daily_sales):.2f}")  # 標準偏差の2乗

 

以下、実行結果です。

合計: 500個
平均: 50.0個
中央値: 50.0個
標準偏差: 4.54
分散: 20.60

 

最大値や最小値、そしてそれらが何日目に発生したかも簡単に分かります:

print(f"最大値: {np.max(daily_sales)}個")
print(f"最小値: {np.min(daily_sales)}個")
print(f"範囲: {np.ptp(daily_sales)}個")  # peak to peak(最大値 - 最小値)

# 位置(例では日)も知りたい場合
print(f"最大値の日: {np.argmax(daily_sales) + 1}日目")  # +1 して出力
print(f"最小値の日: {np.argmin(daily_sales) + 1}日目")  # +1 して出力

 

以下、実行結果です。

最大値: 58個
最小値: 42個
範囲: 16個
最大値の日: 6日目
最小値の日: 5日目

 

累積和(日々の売上を足していく)は、cumsum()で簡単に求めることができます。

# 累積和(日々の売上を足していく)
cumulative_sales = np.cumsum(daily_sales)

print(f"累積売上: {cumulative_sales}")

 

以下、実行結果です。

累積売上: [ 45  97 145 200 242 300 351 400 453 500]

 

 2次元配列:表形式のデータを扱う

これまで1次元の配列(ベクトル)を扱ってきましたが、NumPyは2次元配列(行列)も扱えます。

これは、ExcelやGoogleスプレッドシートのような表形式のデータを表現するのに便利です。

まず、2次元配列を作ってみましょう。

# 2次元配列の作成
# 3店舗×4四半期の売上データ(百万円)
sales_matrix = np.array([
    [120, 135, 128, 142],  # 店舗A
    [98,  102, 110, 115],  # 店舗B
    [150, 145, 160, 170]   # 店舗C
])

print("売上表(店舗×四半期):")
print(sales_matrix)

 

以下、実行結果です。

売上表(店舗×四半期):
[[120 135 128 142]
 [ 98 102 110 115]
 [150 145 160 170]]

 

配列の形状や次元数を確認できます。

print(f"配列の形状: {sales_matrix.shape}")  # 行と列の数
print(f"配列の次元数: {sales_matrix.ndim}")  # 次元の数
print(f"要素の総数: {sales_matrix.size}")  # 要素の数

 

以下、実行結果です。

配列の形状: (3, 4)
配列の次元数: 2
要素の総数: 12

 

2次元配列の要素にアクセスするには、行と列の両方のインデックスを指定します。

# 特定の要素へのアクセス
print(f"\n店舗Aの第2四半期: {sales_matrix[0, 1]}百万円")  # [行, 列]
print(f"店舗Cの第4四半期: {sales_matrix[2, 3]}百万円")

# 行や列を取り出す
print(f"\n店舗Bの全四半期: {sales_matrix[1, :]}")  # 1行目のすべての列
print(f"第3四半期の全店舗: {sales_matrix[:, 2]}")  # すべての行の2列目

 

以下、実行結果です。

店舗Aの第2四半期: 135百万円
店舗Cの第4四半期: 170百万円

店舗Bの全四半期: [ 98 102 110 115]
第3四半期の全店舗: [128 110 160]

 

2次元配列でも統計計算ができます。ここで重要なのがaxisパラメータです。

# 統計を計算
print("統計分析:")
# 各店舗の年間売上(行ごとの合計)
annual_sales = np.sum(sales_matrix, axis=1)  # axis=1 は横方向(列方向)に計算
print(f"各店舗の年間売上: {annual_sales}")

# 各四半期の全店舗合計(列ごとの合計)
quarterly_totals = np.sum(sales_matrix, axis=0)  # axis=0 は縦方向(行方向)に計算
print(f"四半期別の全店舗合計: {quarterly_totals}")

 

以下、実行結果です。

統計分析:
各店舗の年間売上: [525 425 625]
四半期別の全店舗合計: [368 382 398 427]

 

axisパラメータは最初は混乱しやすいので、少し丁寧に説明します。

axis=0は「行を縦断する」(縦方向)、axis=1は「列を横断する」(横方向)と覚えてください。

つまり、axis=0で合計すると各列の合計(縦に足す)、axis=1で合計すると各行の合計(横に足す)になります。

 

Pandas入門:データ分析のスイスアーミーナイフ

 Pandasとは何か、なぜ強力なのか

NumPyが数値計算の基礎だとすれば、Pandasはより実践的なデータ分析のためのツールです。

Pandasの名前は「Panel Data」(パネルデータ:時系列×断面のデータ)から来ていますが、かわいいパンダのイメージも相まって、親しみやすいライブラリとして知られています。

Pandasの最大の特徴は、データに「ラベル」をつけて管理できることです。

NumPyでは数値のインデックス(0, 1, 2…)でデータにアクセスしましたが、Pandasでは「田中さん」「2024年1月」のような意味のあるラベルでデータにアクセスできます。

まず、Pandasをインポートしましょう。

# Pandasをインポート
# 慣習的に pd という短い名前をつけます
import pandas as pd

# バージョン確認
print(f"Pandasのバージョン: {pd.__version__}")

# PandasはNumPyを基盤としているので、NumPyも一緒にインポートしておきます
import numpy as np

print("\nPandasを使ったデータ分析の準備が整いました!")

 

以下、実行結果です。人によってバージョンは異なります。

Pandasのバージョン: 2.1.4

 

 Series:ラベル付きの1次元データ

PandasのSeriesは、NumPyの1次元配列にインデックス(ラベル)をつけたものです。

辞書のように名前でアクセスできる配列、と考えると分かりやすいでしょう。

最初は、デフォルトのインデックス(0, 1, 2…)でSeriesを作ってみます。

# Seriesの作成方法1:リストから作成
temperatures = pd.Series([25.5, 26.2, 24.8, 27.1, 26.5])

print("気温データ(デフォルトインデックス):")
print(temperatures)

 

以下、実行結果です。

気温データ(デフォルトインデックス):
0    25.5
1    26.2
2    24.8
3    27.1
4    26.5
dtype: float64

 

Seriesの構成要素を確認してみましょう。

print("インデックス:", temperatures.index)
print("値:", temperatures.values)  # NumPy配列として取得
print("データ型:", temperatures.dtype)

 

以下、実行結果です。

インデックス: RangeIndex(start=0, stop=5, step=1)
値: [25.5 26.2 24.8 27.1 26.5]
データ型: float64

 

次に、意味のあるインデックスを設定してみます。

# Seriesの作成方法2:インデックスを指定
days = ['月曜', '火曜', '水曜', '木曜', '金曜']
temperatures_labeled = pd.Series(
    [25.5, 26.2, 24.8, 27.1, 26.5], 
    index=days
)
print("曜日ラベル付き気温データ:")
print(temperatures_labeled)

 

以下、実行結果です。

曜日ラベル付き気温データ:
月曜    25.5
火曜    26.2
水曜    24.8
木曜    27.1
金曜    26.5
dtype: float64

 

ラベルを使ってデータにアクセスできるようになりました。

# ラベルでアクセス
print(f"水曜日の気温: {temperatures_labeled['水曜']}℃")
print(f"金曜日の気温: {temperatures_labeled['金曜']}℃")

# 複数の要素を取り出す
print("\n週の前半の気温:")
print(temperatures_labeled[['月曜', '火曜', '水曜']])

 

以下、実行結果です。

水曜日の気温: 24.8℃
金曜日の気温: 26.5℃

週の前半の気温:
月曜    25.5
火曜    26.2
水曜    24.8
dtype: float64

 

実務では、辞書からSeriesを作ることが多いです。

# Seriesの作成方法3:辞書から作成(最も実用的)
sales_dict = {
    '東京店': 520,
    '大阪店': 380,
    '名古屋店': 290,
    '福岡店': 310,
    '札幌店': 250
}
sales_series = pd.Series(sales_dict)

print("店舗別売上(万円):")
print(sales_series)

 

以下、実行結果です。

店舗別売上(万円):
東京店     520
大阪店     380
名古屋店    290
福岡店     310
札幌店     250
dtype: int64

 

Seriesには便利な統計関数が組み込まれています。

# Seriesの便利な統計関数
print("基本統計量:")
print(f"平均売上: {sales_series.mean():.1f}万円")
print(f"最大売上: {sales_series.max()}万円")
print(f"最小売上: {sales_series.min()}万円")
print(f"標準偏差: {sales_series.std():.2f}")

 

以下、実行結果です。

基本統計量:
平均売上: 350.0万円
最大売上: 520万円
最小売上: 250万円
標準偏差: 106.07

 

どの店舗が最高・最低の売上かも簡単に分かります。idxmax()で値の最も大きいインデックスを、idxmin()で値の最も小さいインデックスを取得します。

# どの店舗が最大/最小か
print(f"最高売上の店舗: {sales_series.idxmax()}")
print(f"最低売上の店舗: {sales_series.idxmin()}")

 

以下、実行結果です。

最高売上の店舗: 東京店
最低売上の店舗: 札幌店

 

条件を設定しデータを絞り込むこともできます。

# 条件でフィルタリング
print("売上300万円以上の店舗:")
high_sales = sales_series[sales_series >= 300]
print(high_sales)

 

以下、実行結果です。

売上300万円以上の店舗:
東京店    520
大阪店    380
福岡店    310
dtype: int64

 

 DataFrame:Pandasの主役、表形式データの管理

DataFrameは、Pandasの中心となるデータ構造です。

これは、Excelのシートのような2次元の表形式データを扱うためのものです。

各列がSeries、全体がDataFrameと考えると理解しやすいでしょう。

まず、辞書からDataFrameを作ってみます。これが最も一般的な方法です。

# DataFrameの作成方法1:辞書から作成(最も一般的)
data = {
    '名前': ['田中', '山田', '鈴木', '佐藤', '高橋'],
    '年齢': [25, 32, 28, 35, 29],
    '部署': ['営業', '開発', '営業', '人事', '開発'],
    '売上': [520, 0, 380, 0, 0],  # 営業以外は0
    '評価': ['A', 'B', 'A', 'B', 'A']
}
df = pd.DataFrame(data)

print("社員データ:")
print(df)

 

以下、実行結果です。

社員データ:
   名前  年齢  部署   売上 評価
0  田中  25  営業  520  A
1  山田  32  開発    0  B
2  鈴木  28  営業  380  A
3  佐藤  35  人事    0  B
4  高橋  29  開発    0  A

 

DataFrameの基本情報を確認してみましょう。

print("データフレームの形状:", df.shape)  # (行数, 列数)
print("列名:", df.columns.tolist())
print("インデックス:", df.index.tolist())

 

以下、実行結果です。

データフレームの形状: (5, 5)
列名: ['名前', '年齢', '部署', '売上', '評価']
インデックス: [0, 1, 2, 3, 4]

 

より詳しい情報はinfo()で見ることもできます。

# より詳しい情報を見る
print("データフレームの情報:")
print(df.info())  # 各列のデータ型、非欠損値の数など

 

以下、実行結果です。

データフレームの情報:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   名前      5 non-null      object
 1   年齢      5 non-null      int64 
 2   部署      5 non-null      object
 3   売上      5 non-null      int64 
 4   評価      5 non-null      object
dtypes: int64(2), object(3)
memory usage: 328.0+ bytes
None

 

数値列の統計情報をdescribe()で一度に確認できます。

print("基本統計量(数値列のみ):")
print(df.describe())  # 数値列の統計情報

 

以下、実行結果です。

基本統計量(数値列のみ):
              年齢          売上
count   5.000000    5.000000
mean   29.800000  180.000000
std     3.834058  251.396102
min    25.000000    0.000000
25%    28.000000    0.000000
50%    29.000000    0.000000
75%    32.000000  380.000000
max    35.000000  520.000000

 

DataFrameから特定の列を取り出してみましょう。列を取り出すとき、Seriesとして取得する方法と、DataFrameとして取得する方法があります。

# 特定の列にアクセス
print("名前の列(Series として取得し出力):")
print(df['名前']) 

print("\n名前の列(DataFrame として取得し出力):")
print(df[['名前']]) 

 

以下、実行結果です。

名前の列(Series として取得し出力):
0    田中
1    山田
2    鈴木
3    佐藤
4    高橋
Name: 名前, dtype: object

名前の列(DataFrame として取得し出力):
   名前
0  田中
1  山田
2  鈴木
3  佐藤
4  高橋

 

複数の列を同時に選択することもできます。

# 複数列を選択(DataFrame として取得し出力)
print("名前と部署:")
print(df[['名前', '部署']]) 

 

以下、実行結果です。

名前と部署:
   名前  部署
0  田中  営業
1  山田  開発
2  鈴木  営業
3  佐藤  人事
4  高橋  開発

 

新しい列を追加することも簡単です。

# 新しい列を追加
df['勤続年数'] = [3, 8, 5, 10, 4]

print("勤続年数を追加:")
print(df)

 

以下、実行結果です。

勤続年数を追加:
   名前  年齢  部署   売上 評価  勤続年数
0  田中  25  営業  520  A     3
1  山田  32  開発    0  B     8
2  鈴木  28  営業  380  A     5
3  佐藤  35  人事    0  B    10
4  高橋  29  開発    0  A     4

 

既存の列を使って計算した結果を新しい列にすることもできます。

# 計算による新しい列の作成
df['年収予測'] = df['年齢'] * 20 + df['勤続年数'] * 30  # 簡単な計算式

print("年収予測を追加:")
print(df)

 

以下、実行結果です。

年収予測を追加:
   名前  年齢  部署   売上 評価  勤続年数  年収予測
0  田中  25  営業  520  A     3   590
1  山田  32  開発    0  B     8   880
2  鈴木  28  営業  380  A     5   710
3  佐藤  35  人事    0  B    10  1000
4  高橋  29  開発    0  A     4   700

 

 DataFrameの行と列へのアクセス

DataFrameの行にアクセスする方法はいくつかあります。

まず、ilocを利用した位置ベースのアクセス方法を見てみましょう。

# iloc:位置ベースのインデックス(0から始まる番号)
print("2行目のデータ(iloc[1]):")
print(df.iloc[1])  # Series として取得
print("-"*50)
print("1~3行目(iloc[0:3]):")
print(df.iloc[0:3])  # DataFrame として取得

 

以下、実行結果です。

2行目のデータ(iloc[1]):
名前       山田
年齢       32
部署       開発
売上        0
評価        B
勤続年数      8
年収予測    880
Name: 1, dtype: object
--------------------------------------------------
1~3行目(iloc[0:3]):
   名前  年齢  部署   売上 評価  勤続年数  年収予測
0  田中  25  営業  520  A     3   590
1  山田  32  開発    0  B     8   880
2  鈴木  28  営業  380  A     5   710

 

次に、locを利用したラベルベースのアクセス方法です。

まず、分かりやすいインデックスを設定します。

# loc:ラベルベースのインデックス
# まず、分かりやすいインデックスを設定
df_indexed = df.set_index('名前')
print("名前をインデックスに設定:")
print(df_indexed)
print("-"*50)
print("山田さんのデータ(loc['山田']):")
print(df_indexed.loc['山田'])

 

以下、実行結果です。

名前をインデックスに設定:
    年齢  部署   売上 評価  勤続年数  年収予測
名前                            
田中  25  営業  520  A     3   590
山田  32  開発    0  B     8   880
鈴木  28  営業  380  A     5   710
佐藤  35  人事    0  B    10  1000
高橋  29  開発    0  A     4   700
--------------------------------------------------
山田さんのデータ(loc['山田']):
年齢       32
部署       開発
売上        0
評価        B
勤続年数      8
年収予測    880
Name: 山田, dtype: object

 

最もよく使うのは、条件による行の選択です。

# 条件による行の選択(最もよく使う)
print("営業部の社員:")
sales_dept = df[df['部署'] == '営業']
print(sales_dept)

 

以下、実行結果です。

営業部の社員:
   名前  年齢  部署   売上 評価  勤続年数  年収予測
0  田中  25  営業  520  A     3   590
2  鈴木  28  営業  380  A     5   710

 

年齢による条件でも見てみましょう。

print("30歳以上の社員:")
over_30 = df[df['年齢'] >= 30]
print(over_30)

 

以下、実行結果です。

30歳以上の社員:
   名前  年齢  部署  売上 評価  勤続年数  年収予測
1  山田  32  開発   0  B     8   880
3  佐藤  35  人事   0  B    10  1000

 

複数の条件を組み合わせることもできます。

# 複数条件の組み合わせ
print("営業部かつ評価Aの社員:")
condition = (df['部署'] == '営業') &amp; (df['評価'] == 'A')  # 条件設定
excellent_sales = df[condition]  # 条件適用
print(excellent_sales)

 

以下、実行結果です。

営業部かつ評価Aの社員:
   名前  年齢  部署   売上 評価  勤続年数  年収予測
0  田中  25  営業  520  A     3   590
2  鈴木  28  営業  380  A     5   710

 

 データの読み込みと保存

実際のデータ分析では、CSV(カンマ区切り)やExcelファイルからデータを読み込むことが多いです。

まず、サンプルデータを作成して保存してみましょう。

# サンプルデータを作成
sample_data = pd.DataFrame({
    '日付': pd.date_range('2024-01-01', periods=7),  # 7日分の日付を生成
    '売上': [45000, 52000, 48000, 55000, 51000, 58000, 62000],
    '来客数': [120, 135, 125, 142, 130, 145, 155],
    '平均単価': [3750, 3852, 3840, 3873, 3923, 4000, 4000]
})

print("サンプルデータ:")
print(sample_data)

 

以下、実行結果です。

サンプルデータ:
          日付     売上  来客数  平均単価
0 2024-01-01  45000  120  3750
1 2024-01-02  52000  135  3852
2 2024-01-03  48000  125  3840
3 2024-01-04  55000  142  3873
4 2024-01-05  51000  130  3923
5 2024-01-06  58000  145  4000
6 2024-01-07  62000  155  4000

 

このデータフレームをto_csvでCSVファイルとして保存します。

# CSVファイルとして保存
sample_data.to_csv(
    'sales_data.csv', 
    index=False, 
    encoding='utf-8-sig'
)
# index=False: インデックス列を保存しない
# encoding='utf-8-sig': 日本語を含む場合の文字化け対策

print("CSVファイルを保存しました: sales_data.csv")

 

以下、実行結果です。

CSVファイルを保存しました: sales_data.csv

 

保存したCSVファイルをread_csvで読み込んでみましょう。

# CSVファイルを読み込む
df_loaded = pd.read_csv('sales_data.csv')
print("読み込んだデータ:")
print(df_loaded)

# データ型を確認
print("\nデータ型:")
print(df_loaded.dtypes)

 

以下、実行結果です。

読み込んだデータ:
           日付     売上  来客数  平均単価
0  2024-01-01  45000  120  3750
1  2024-01-02  52000  135  3852
2  2024-01-03  48000  125  3840
3  2024-01-04  55000  142  3873
4  2024-01-05  51000  130  3923
5  2024-01-06  58000  145  4000
6  2024-01-07  62000  155  4000

データ型:
日付      object
売上       int64
来客数      int64
平均単価     int64
dtype: object

 

日付列が文字列として読み込まれているので、to_datetimeを使い適切な型に変換(文字列を日付に変換)します。

# 日付列を datetime 型に変換(より適切な型に)
df_loaded['日付'] = pd.to_datetime(df_loaded['日付'])

print("日付型変換後:")
print(df_loaded.dtypes)

 

以下、実行結果です。

日付型変換後:
日付      datetime64[ns]
売上               int64
来客数              int64
平均単価             int64
dtype: object

 

 データのクリーニング:欠損値の処理

実際のデータには、よく欠損値(missing value)が含まれています。

欠損値とは、何らかの理由でデータが記録されていない部分のことです。

欠損値を含むデータを作成してみましょう。

# 欠損値を含むデータを作成
data_with_missing = pd.DataFrame({
    '商品': ['A', 'B', 'C', 'D', 'E'],
    '1月売上': [100, 150, None, 200, 180],  # None は欠損値
    '2月売上': [110, None, 95, 210, None],
    '3月売上': [120, 160, 100, None, 200]
})

print("欠損値を含むデータ:")
print(data_with_missing)

 

以下、実行結果です。

欠損値を含むデータ:
  商品   1月売上   2月売上   3月売上
0  A  100.0  110.0  120.0
1  B  150.0    NaN  160.0
2  C    NaN   95.0  100.0
3  D  200.0  210.0    NaN
4  E  180.0    NaN  200.0

 

欠損値がどこにあるか確認します。

print("欠損値の有無:")
print(data_with_missing.isnull())  # 欠損値は True で表示

print("\n各列の欠損値の数:")
print(data_with_missing.isnull().sum()) # Trueの数(欠損値の数)を合計

 

以下、実行結果です。

欠損値の有無:
      商品   1月売上   2月売上   3月売上
0  False  False  False  False
1  False  False   True  False
2  False   True  False  False
3  False  False  False   True
4  False  False   True  False

各列の欠損値の数:
商品      0
1月売上    1
2月売上    2
3月売上    1
dtype: int64

 

欠損値を処理する方法はいくつかあります。

まず、欠損値を含む行をdropna()で削除する方法です。

# 欠損値の処理方法1:削除
# 欠損値を含む行をすべて削除
df_dropped = data_with_missing.dropna()

print("\n欠損値を含む行を削除:")
print(df_dropped)

 

以下、実行結果です。

欠損値を含む行を削除:
  商品   1月売上   2月売上   3月売上
0  A  100.0  110.0  120.0

 

次に、fillna()で欠損値を特定の値で埋める方法です。

# 欠損値の処理方法2:穴埋め(補完)
# 0で埋める
df_filled_zero = data_with_missing.fillna(0)
print("\n欠損値を0で埋める:")
print(df_filled_zero)

 

以下、実行結果です。

欠損値を0で埋める:
  商品   1月売上   2月売上   3月売上
0  A  100.0  110.0  120.0
1  B  150.0    0.0  160.0
2  C    0.0   95.0  100.0
3  D  200.0  210.0    0.0
4  E  180.0    0.0  200.0

 

数値列の各列の平均値で埋めることもできます。

# 各列の平均値で埋める(数値列のみ)
numeric_columns = ['1月売上', '2月売上', '3月売上']
df_filled_mean = data_with_missing.fillna(
    data_with_missing[numeric_columns].mean()
)

print("欠損値を平均値で埋める:")
print(df_filled_mean)

 

以下、実行結果です。

欠損値を平均値で埋める:
  商品   1月売上        2月売上   3月売上
0  A  100.0  110.000000  120.0
1  B  150.0  138.333333  160.0
2  C  157.5   95.000000  100.0
3  D  200.0  210.000000  145.0
4  E  180.0  138.333333  200.0

 

 グループ化と集計:データを深く理解する

データ分析でよく行う操作の一つが、カテゴリごとにデータをグループ化して集計することです。

より大きなサンプルデータを作成して、実践してみましょう。

# より大きなサンプルデータを作成
np.random.seed(42)  # 乱数を固定(結果を再現可能にする)

employees = pd.DataFrame({
    '名前': [f'社員{i+1}' for i in range(120)],
    '部署': np.random.choice(
        ['営業', '開発', '人事', '経理'], 
        120
    ),
    '年齢': np.random.randint(22, 60, 120),
    '勤続年数': np.random.randint(0, 30, 120),
    '月給': np.random.randint(20, 80, 120) * 10000,
    '評価': np.random.choice(
        ['S', 'A', 'B', 'C'], 
        120, 
        p=[0.1, 0.3, 0.4, 0.2]
    )
})

print("社員データ(120人):")
print(employees.head(10))  # 最初の10人だけ表示
print("...")
print(f"全{len(employees)}人のデータ")

 

以下、実行結果です。

社員データ(120人):
     名前  部署  年齢  勤続年数      月給 評価
0   社員1  人事  37     0  460000  B
1   社員2  経理  39     8  460000  A
2   社員3  営業  45    27  530000  B
3   社員4  人事  47    26  400000  S
4   社員5  人事  46     5  490000  S
5   社員6  経理  50    15  520000  B
6   社員7  営業  36    28  470000  B
7   社員8  営業  22     2  660000  B
8   社員9  人事  46    19  520000  B
9  社員10  開発  28    27  240000  C
...
全120人のデータ

 

groupby()で部署ごとにデータをグループ化してみましょう。

# 部署ごとにグループ化
grouped = employees.groupby('部署')

# 部署ごとの人数
print("部署ごとの人数:")
print(grouped.size())

 

以下、実行結果です。

部署ごとの人数:
部署
人事    28
営業    22
経理    36
開発    34
dtype: int64

 

各部署の平均値を計算します。

# 部署ごとの平均値
print("部署ごとの平均:")
print(grouped[
        ['年齢', '勤続年数', '月給']
    ].mean().round(1)
)

 

以下、実行結果です。

部署ごとの平均:
      年齢  勤続年数        月給
部署                      
人事  39.0  15.3  521785.7
営業  43.4  15.2  528181.8
経理  40.9  14.5  510555.6
開発  39.6  12.1  520000.0

 

複数の集計をagg()で同時に行うこともできます。

# 複数の集計を同時に行う
print("部署ごとの詳細統計:")
agg_result = grouped.agg({
    '年齢': ['mean', 'min', 'max'],
    '月給': ['mean', 'sum'],
    '名前': 'count'  # 人数をカウント
})
print(agg_result)

 

以下、実行結果です。

部署ごとの詳細統計:
           年齢                     月給              名前
         mean min max           mean       sum count
部署                                                  
人事  39.035714  22  56  521785.714286  14610000    28
営業  43.409091  22  58  528181.818182  11620000    22
経理  40.888889  22  59  510555.555556  18380000    36
開発  39.617647  22  58  520000.000000  17680000    34

 

評価別の分析もしてみましょう。sort_values(ascending=False)で平均月給の降順で並び替えています。

# 評価ごとの平均月給
print("評価ごとの平均月給:")
eval_salary = employees.groupby('評価')['月給'
    ].mean().sort_values(ascending=False)
print(eval_salary)

 

以下、実行結果です。

評価ごとの平均月給:
評価
A    528965.517241
C    523333.333333
S    517777.777778
B    511632.653061
Name: 月給, dtype: float64

 

クロス集計(ピボットテーブル)をcrosstab()で作ることもできます。

# クロス集計(ピボットテーブル)
print("部署×評価のクロス集計(人数):")
pivot = pd.crosstab(
    employees['部署'], 
    employees['評価']
)
print(pivot)

 

以下、実行結果です。

部署×評価のクロス集計(人数):
評価   A   B  C  S
部署              
人事   6  11  4  7
営業   5  10  5  2
経理   8  15  8  5
開発  10  13  7  4

 

まとめ

今回は、データ分析の核となるNumPyとPandasの基本的な使い方を説明しました。

NumPyでは配列の操作と高速な数値計算を、Pandasでは表形式データの読み込み、操作、集計を解説しました。

特に重要なのは、これらのライブラリが単なる計算ツールではなく、「データを理解し、洞察を得るための思考を支援するツール」だということです。

データをグループ化して平均を求めたり、欠損値を適切に処理したり、条件でデータを絞り込んだりする操作一つ一つが、ビジネスの課題解決に直結します。

今回学んだ技術は、売上分析、顧客分析、マーケティング効果測定、品質管理など、あらゆるビジネス分野で活用できます。

まずは身近なデータ(家計簿、売上データ、アンケート結果など)で今回の操作を試してみてください。

データが「数字の羅列」から「意味のある情報」に変わる瞬間を体感できるはずです。

次回は、データ可視化の世界に踏み込み、グラフやチャートを使ってデータを「見える化」する方法を学んでいきます。

数値だけでは伝わらないデータの背後にある物語を、視覚的に表現する技術を身につけましょう。