Pythonでデータ分析を行う際、最もよく使うライブラリが pandasです。
そして、データ分析の多くは CSVファイル を読み込むところから始まります。
CSVファイルとは、「Comma Separated Values」の略で、データをカンマ(,)で区切って保存したテキストファイルのことです。
以下は、最もシンプルなCSVファイルの読み込み方法です。たった1行で読み込めることがわかります。
# pandasライブラリをインポートする
# 慣例として「pd」という短い名前で使えるようにする
import pandas as pd
# CSVファイルを読み込んでDataFrame(表形式のデータ)として変数dfに格納する
df = pd.read_csv("data.csv")
このたった1行でCSVファイルを読み込めます。
しかし、実務では「文字化けする」「数値が文字列として読み込まれる」「日付がうまく認識されない」といった問題に必ず直面します。
そこで活躍するのが、今回紹介する オプション(引数)たちです。
サンプルデータのダウンロード
今回利用するサンプルCSVファイルを、以下のリンクからダウンロードできます。
ダウンロード: sales_data.csv(https://www.salesanalytics.co.jp/a2yi)
このファイルは架空のECサイトの売上データで、以下のような特徴を持っています。
encoding:文字化けを防ぐ
まず、何も考えずにサンプルデータを読み込んでみましょう。
何が起きるか確認します。
import pandas as pd
# デフォルト(UTF-8エンコーディング)で読み込もうとする
df = pd.read_csv("sales_data.csv")
このコードを実行すると、おそらく以下のようなエラーが出るはずです。
UnicodeDecodeError: 'utf-8' codec can't decode byte...
これは エンコーディング(文字の保存形式)の不一致が原因です。
サンプルファイルは日本のビジネス環境でよく使われる「cp932」形式で保存されていますが、pandasはデフォルトで「UTF-8」形式になっています。
エラーを解決するために、encodingオプションを指定して読み込みます。
import pandas as pd
# encoding="cp932" を指定して読み込む
df = pd.read_csv("sales_data.csv", encoding="cp932")
# 正常に読み込めたかどうか、先頭5行を表示して確認する
print(df.head())
上記のコードを実行すると、以下のように正常に読み込まれた結果が表示されます。
顧客ID 顧客名 郵便番号 都道府県 商品コード 商品名 注文日 発送日 単価 \ 0 C001 山田太郎 120001 北海道 1 ノートパソコン 2024-01-15 2024-01-17 89800 1 C002 鈴木花子 1500001 東京都 2 マウス 2024-01-16 2024-01-18 2980 2 C003 佐藤健一 5300001 大阪府 3 キーボード 2024-01-17 未入力 5980 3 C004 田中美咲 8100001 福岡県 1 ノートパソコン 2024-01-18 2024-01-20 89800 4 C005 高橋誠 600001 北海道 4 モニター 2024-01-19 - 34800 個数 売上 備考 0 1 89800 新規顧客 1 3 8940 リピーター 2 2 11960 発送待ち 3 1 89800 NaN 4 1 34800 在庫確認中
文字化けせずに読み込めました。
以下は、よく使うエンコーディングです。
| エンコーディング | 説明 |
|---|---|
| utf-8 | 国際標準。最近のファイルはこれが多い(デフォルト) |
| cp932 / shift_jis | Windows環境の日本語ファイルでよく使われる |
| euc-jp |
どのエンコーディングかわからない場合は、まず cp932 を試してみましょう。
日本のビジネス環境ではExcel由来のCSVが非常に多いためです。
dtype:データ型を明示的に指定する
先ほどの実行結果をよく見てください。
郵便番号の列に問題があることに気づきましたか?
詳しく確認してみましょう。
import pandas as pd
# エンコーディングを指定してCSVを読み込む
df = pd.read_csv("sales_data.csv", encoding="cp932")
# 確認したい列だけを選択して先頭5行を表示する
print(df[["顧客ID", "顧客名", "郵便番号", "商品コード"]].head())
上記のコードを実行すると、以下の結果が表示されます。よく見てください。
顧客ID 顧客名 郵便番号 商品コード 0 C001 山田太郎 120001 1 1 C002 鈴木花子 1500001 2 2 C003 佐藤健一 5300001 3 3 C004 田中美咲 8100001 1 4 C005 高橋誠 600001 4
山田太郎さんの郵便番号は本来「0120001」(北海道)ですが、「120001」になっています。
高橋誠さんも「0600001」が「600001」に。
商品コードも「001」「002」が「1」「2」になっています。
pandasが「数値だ」と判断して、先頭のゼロを削ってしまったのです。
この問題を解決するために、dtypeオプションで特定の列を文字列として読み込むよう指定します。
import pandas as pd
# 郵便番号と商品コードを文字列(str)として読み込む
# dtype引数には辞書形式で「列名: データ型」を指定する
df = pd.read_csv(
"sales_data.csv",
encoding="cp932",
dtype={"郵便番号": str, "商品コード": str} # ← これを追加!
)
# 結果を確認する
print(df[["顧客ID", "顧客名", "郵便番号", "商品コード"]].head())
上記のコードを実行すると、先頭のゼロが保持された状態で表示されます。
顧客ID 顧客名 郵便番号 商品コード 0 C001 山田太郎 0120001 001 1 C002 鈴木花子 1500001 002 2 C003 佐藤健一 5300001 003 3 C004 田中美咲 8100001 001 4 C005 高橋誠 0600001 004
先頭のゼロが保持されました。
以下は、よく使うデータ型です。
| 指定方法 | 意味 |
|---|---|
| str | 文字列(テキスト) |
| int | 整数(小数点なし) |
| float | 浮動小数点数(小数点あり) |
| bool |
「IDっぽい列」「コードっぽい列」は、とりあえず `str` で読み込んでおくと安全です。
数値として計算する必要がなければ、文字列のままにしておきましょう。
parse_dates:日付データを正しく認識させる
サンプルデータには「注文日」「発送日」という日付の列があります。
pandasがこれらをどのようなデータ型として認識しているか確認してみましょう。
import pandas as pd
# これまでのオプションを適用してCSVを読み込む
df = pd.read_csv(
"sales_data.csv",
encoding="cp932",
dtype={"郵便番号": str, "商品コード": str}
)
# 注文日と発送日のデータ型を確認する
print(df[["注文日", "発送日"]].dtypes)
上記のコードを実行すると、データ型が表示されます。
注文日 object 発送日 object dtype: object
「object」というのは、pandasでは基本的に「文字列」を意味します。
つまり、日付が文字列として読み込まれているのです。
文字列のままだと、「1ヶ月前のデータだけ抽出」「曜日ごとに集計」といった時系列分析ができません。
parse_datesオプションを使って、日付として認識させたい列を指定します。
import pandas as pd
# 注文日を日付型として読み込む
df = pd.read_csv(
"sales_data.csv",
encoding="cp932",
dtype={"郵便番号": str, "商品コード": str},
parse_dates=["注文日"] # ← これを追加
)
# データ型を確認する
print("注文日のデータ型:", df["注文日"].dtype)
datetime64[ns] と表示されれば日付型として認識されています。
注文日のデータ型: datetime64[ns]
日付型になっていると、特定の期間のデータを簡単に抽出できます。
# 2024年1月20日以降の注文だけを抽出する
# 日付型の列は文字列 "2024-01-20" と比較できる
recent_orders = df[df["注文日"] >= "2024-01-20"]
# 抽出結果を確認する
print(f"2024年1月20日以降の注文数: {len(recent_orders)}件")
print()
# 必要な列だけを表示する
print(recent_orders[["顧客ID", "顧客名", "注文日", "商品名"]])
上記のコードを実行すると、条件に合致するデータだけが抽出されます。
2024年1月20日以降の注文数: 10件
顧客ID 顧客名 注文日 商品名
5 C006 伊藤裕子 2024-01-20 マウス
6 C007 渡辺翔太 2024-01-21 USBハブ
7 C008 中村あおい 2024-01-22 キーボード
8 C009 小林大輔 2024-01-23 ウェブカメラ
9 C010 加藤めぐみ 2024-01-24 ノートパソコン
10 C011 吉田拓也 2024-01-25 モニター
11 C012 山本さくら 2024-01-26 マウス
12 C013 松本健太 2024-01-27 ヘッドセット
13 C014 井上優子 2024-01-28 USBハブ
14 C015 木村俊介 2024-01-29 外付けSSD
「発送日」列には「未入力」「-」「不明」などの文字列が混在しているため、parse_dates に含めると変換エラーになります。
このようなケースは、読み込み後に pd.to_datetime() を使って個別に処理するのがおすすめです。
na_values:欠損値として扱う文字列を指定する
サンプルデータの「発送日」列には、様々な形式で「データがない」ことが表現されています。
確認してみましょう。
import pandas as pd
# これまでのオプションを適用してCSVを読み込む
df = pd.read_csv(
"sales_data.csv",
encoding="cp932",
dtype={"郵便番号": str, "商品コード": str},
parse_dates=["注文日"]
)
# 発送日列のユニークな値(重複を除いた一覧)を確認する
# unique()メソッドで重複のない値の配列が得られる
print("発送日のユニークな値:")
print(df["発送日"].unique())
上記のコードを実行すると、様々な欠損値表記が混在していることがわかります。
発送日のユニークな値: ['2024-01-17' '2024-01-18' '未入力' '2024-01-20' '-' '2024-01-22' '2024-01-23' '不明' '2024-01-25' '2024-01-26' '999' '2024-01-28' '2024-01-29' '2024-01-31']
「未入力」「-」「不明」「999」など、様々な形式で「データがない」ことを表現しています。
これらはすべて 欠損値 として統一して扱いたいところです。
na_valuesオプションで、欠損値として扱いたい文字列をリストで指定します。
import pandas as pd
# 欠損値として扱いたい文字列をna_valuesで指定する
df = pd.read_csv(
"sales_data.csv",
encoding="cp932",
dtype={"郵便番号": str, "商品コード": str},
parse_dates=["注文日"],
na_values=["未入力", "-", "不明", "999"] # ← これを追加
)
# 発送日の値を再確認する
# 指定した文字列が nan(欠損値)に変換されているはず
print("発送日のユニークな値:")
print(df["発送日"].unique())
print()
# 欠損値の数を確認する
# isnull()で欠損値かどうかを判定し、sum()で合計を取る
print("発送日の欠損値の数:", df["発送日"].isnull().sum())
上記のコードを実行すると、指定した文字列がすべて欠損値に変換されていることがわかります。
発送日のユニークな値: ['2024-01-17' '2024-01-18' nan '2024-01-20' '2024-01-22' '2024-01-23' '2024-01-25' '2024-01-26' '2024-01-28' '2024-01-29' '2024-01-31'] 発送日の欠損値の数: 5
「未入力」「-」「不明」「999」がすべて `nan`(Not a Number = 欠損値)に変換されました。
欠損値が正しく認識されると、pandasの便利な欠損値処理メソッドが使えるようになります。
# 発送済みのデータ(日付が入っているデータ)だけを抽出する
# notna()は「欠損値ではない」を意味する
shipped = df[df["発送日"].notna()]
print(f"発送済み: {len(shipped)}件")
# 未発送のデータ(日付が欠損)だけを抽出する
# isna()は「欠損値である」を意味する
not_shipped = df[df["発送日"].isna()]
print(f"未発送: {len(not_shipped)}件")
print()
# 未発送の顧客情報を表示する
# どの顧客がどんな理由で未発送なのかを確認できる
print("未発送の顧客:")
print(not_shipped[["顧客ID", "顧客名", "商品名", "備考"]])
上記のコードを実行すると、発送状況でデータを分類できていることがわかります。
発送済み: 10件
未発送: 5件
未発送の顧客:
顧客ID 顧客名 商品名 備考
2 C003 佐藤健一 キーボード 発送待ち
4 C005 高橋誠 モニター 在庫確認中
7 C008 中村あおい キーボード 問い合わせ中
10 C011 吉田拓也 モニター 発送遅延
13 C014 井上優子 USBハブ NaN
データを受け取ったら、まず「欠損値がどのように表現されているか」をチェックする癖をつけましょう。
usecols:必要な列だけを読み込む
サンプルデータには12列ありますが、分析によっては一部の列だけで十分なことも多いです。
まず全体の構造を確認してみましょう。
import pandas as pd
# エンコーディングだけ指定して読み込む(全列読み込み)
df = pd.read_csv("sales_data.csv", encoding="cp932")
# 列数を確認する
# len(df.columns)で列数がわかる
print(f"全列数: {len(df.columns)}列")
# 列名の一覧を確認する
# columns.tolist()でリスト形式で取得できる
print(f"列名: {df.columns.tolist()}")
上記のコードを実行すると、ファイルの構造がわかります。
全列数: 12列 列名: ['顧客ID', '顧客名', '郵便番号', '都道府県', '商品コード', '商品名', '注文日', '発送日', '単価', '個数', '売上', '備考']
必要な列だけを読み込むことで、メモリ使用量を削減し、処理を高速化できます。
usecolsオプションで、読み込みたい列名をリストで指定します。
import pandas as pd
# 売上分析に必要な5列だけを読み込む
# 不要な列(郵便番号、都道府県、発送日、備考など)は読み込まない
df = pd.read_csv(
"sales_data.csv",
encoding="cp932",
dtype={"商品コード": str}, # 商品コードは文字列として
parse_dates=["注文日"], # 注文日は日付として
usecols=["顧客ID", "商品コード", "商品名", "注文日", "売上"] # ← これを追加
)
# 読み込んだ列数を確認する
print(f"読み込んだ列数: {len(df.columns)}列")
print()
# データの内容を確認する
print(df.head())
上記のコードを実行すると、指定した列だけが読み込まれていることがわかります。
読み込んだ列数: 5列 顧客ID 商品コード 商品名 注文日 売上 0 C001 001 ノートパソコン 2024-01-15 89800 1 C002 002 マウス 2024-01-16 8940 2 C003 003 キーボード 2024-01-17 11960 3 C004 001 ノートパソコン 2024-01-18 89800 4 C005 004 モニター 2024-01-19 34800
読み込んだデータを使って、簡単な集計分析をしてみましょう。
# 商品別の売上合計を計算する
# - groupby("商品名")で商品ごとにグループ化し、["売上"].sum()で合計を取る
# - sort_values(ascending=False)で降順(大きい順)にソート
sales_by_product = df.groupby("商品名")["売上"].sum().sort_values(ascending=False)
# 結果を表示する
print("商品別売上合計:")
print(sales_by_product)
上記のコードを実行すると、商品別の売上ランキングがわかります。
商品別売上合計: 商品名 ノートパソコン 359200 モニター 69600 マウス 53640 キーボード 17940 外付けSSD 15800 ヘッドセット 12800 USBハブ 9900 ウェブカメラ 7980 Name: 売上, dtype: int64
最初は全列読み込んで中身を確認し、必要な列がわかったら usecols で絞り込む、という流れがおすすめです。
どんなエンコーディングでも読み込める万能関数
最後に、実務で役立つ便利な関数を紹介します。
CSVファイルのエンコーディングがわからない場合、一つずつ試すのは面倒ですよね。
そこで、複数のエンコーディングを自動的に試して読み込む関数を作りました。
# ================================================
# 万能CSV読み込み関数:smart_read_csv
#
# 機能:複数のエンコーディングを自動的に試して、
# 最初に成功したものでCSVを読み込む
#
# 使い方:
# df = smart_read_csv("ファイル.csv")
# df = smart_read_csv("ファイル.csv", dtype={"ID": str})
# ================================================
import pandas as pd
def smart_read_csv(filepath, **kwargs):
"""
複数のエンコーディングを自動的に試してCSVを読み込む関数
Parameters
----------
filepath : str
CSVファイルのパス
**kwargs : dict
pd.read_csv() に渡す追加の引数(dtype, parse_dates など)
Returns
-------
読み込んだDataFrame
"""
# ================================================
# 試すエンコーディングのリストを定義
# ================================================
encodings = [
"utf-8", # 国際標準(最近のファイルに多い)
"cp932", # Windows日本語(Shift_JISの拡張、Excelでよく使われる)
"shift_jis", # 日本語の標準的なエンコーディング
"euc-jp", # Unix/Linux環境の日本語
"iso-2022-jp", # メール等で使われる日本語
"utf-8-sig", # BOM付きUTF-8(Excel出力でたまにある)
"latin-1", # 西欧言語(バイナリとしても読める、最後の手段)
]
# ================================================
# 各エンコーディングを順番に試す
# ================================================
errors_log = [] # エラーを記録しておく
for encoding in encodings:
try:
# このエンコーディングで読み込みを試みる
df = pd.read_csv(filepath, encoding=encoding, **kwargs)
# 成功した場合:使用したエンコーディングを表示して返す
print(f"✓ 読み込み成功: encoding='{encoding}'")
return df
except UnicodeDecodeError as e:
# 文字コードの問題で失敗した場合:ログに記録して次を試す
errors_log.append(f" × {encoding}: {str(e)[:50]}...")
except Exception as e:
# その他のエラーの場合:ログに記録して次を試す
errors_log.append(f" × {encoding}: {str(e)[:50]}...")
# すべてのエンコーディングで失敗した場合
error_message = "すべてのエンコーディングで読み込みに失敗しました:\n"
error_message += "\n".join(errors_log)
raise ValueError(error_message)
上記のコードを実行すると、自動的に正しいエンコーディングを見つけて読み込んでくれます。
実際に使ってみます。
# ファイルパスを渡すだけで自動的にエンコーディングを判定
df = smart_read_csv("sales_data.csv")
# 読み込み結果を確認
print("=== 読み込み結果 ===")
print(df.head())
以下、実行結果です。
✓ 読み込み成功: encoding='cp932' === 読み込み結果 === 顧客ID 顧客名 郵便番号 都道府県 商品コード 商品名 注文日 発送日 単価 \ 0 C001 山田太郎 120001 北海道 1 ノートパソコン 2024-01-15 2024-01-17 89800 1 C002 鈴木花子 1500001 東京都 2 マウス 2024-01-16 2024-01-18 2980 2 C003 佐藤健一 5300001 大阪府 3 キーボード 2024-01-17 未入力 5980 3 C004 田中美咲 8100001 福岡県 1 ノートパソコン 2024-01-18 2024-01-20 89800 4 C005 高橋誠 600001 北海道 4 モニター 2024-01-19 - 34800 個数 売上 備考 0 1 89800 新規顧客 1 3 8940 リピーター 2 2 11960 発送待ち 3 1 89800 NaN 4 1 34800 在庫確認中
ちなみに、通常のread_csv()と同じオプションが使えます。
df = smart_read_csv(
"sales_data.csv",
dtype={"郵便番号": str, "商品コード": str},
parse_dates=["注文日"],
na_values=["未入力", "-", "不明", "999"]
)
# 読み込み結果を確認
print("=== 読み込み結果 ===")
print(df.head())
print()
# データ型を確認
print("データ型:")
print(df.dtypes)
以下、実行結果です。
✓ 読み込み成功: encoding='cp932'
=== 読み込み結果 ===
顧客ID 顧客名 郵便番号 都道府県 商品コード 商品名 注文日 発送日 単価 個数 \
0 C001 山田太郎 0120001 北海道 001 ノートパソコン 2024-01-15 2024-01-17 89800 1
1 C002 鈴木花子 1500001 東京都 002 マウス 2024-01-16 2024-01-18 2980 3
2 C003 佐藤健一 5300001 大阪府 003 キーボード 2024-01-17 NaN 5980 2
3 C004 田中美咲 8100001 福岡県 001 ノートパソコン 2024-01-18 2024-01-20 89800 1
4 C005 高橋誠 0600001 北海道 004 モニター 2024-01-19 NaN 34800 1
売上 備考
0 89800 新規顧客
1 8940 リピーター
2 11960 発送待ち
3 89800 NaN
4 34800 在庫確認中
データ型:
顧客ID object
顧客名 object
郵便番号 object
都道府県 object
商品コード object
商品名 object
注文日 datetime64[ns]
発送日 object
単価 int64
個数 int64
売上 int64
備考 object
dtype: object
まとめ
今回紹介した5つのオプションをマスターすれば、大抵のCSVファイルは問題なく読み込めるようになります。

