pandasでCSVを読み込む|型変換・欠損値処理の基本【初心者向け】

手元にあるデータを扱いやすい形にするためには、データ読み込みと前処理が必要です。

この記事では、csvファイルの読み込みと型変換、欠損処理の方法を紹介します。

作業環境は、vscode を使用しています。

練習用 CSV を準備する

この記事では以下のサンプルデータを使います。  

下のテキストをコピーして、テキストエディタ(メモ帳など)に貼り付け、sample_data.csv という名前で保存してください。

顧客ID,日付,カテゴリ,売上,担当者
00123,2024/01/05,食品,12000,田中
00456,2024/01/07,日用品,N/A,佐藤
00789,2024/01/10,食品,8500,田中
01011,2024/01/12,,5200,
01234,2024/01/15,日用品3400,鈴木
01567,2024/01/18,食品,15000,佐藤
01890,2024/01/20,家電,N/A,田中
02112,2024/01/22,日用品,9800,
02345,2024/01/25,家電,42000,鈴木
02678,2024/01/28,食品,,田中
02901,2024/02/01,家電,88000,佐藤
03124,2024/02/03,日用品,1200,鈴木
03457,2024/02/05,食品,N/A,
03680,2024/02/08,家電,67000,田中
03903,2024/02/10,食品,4300,佐藤

データを読み込む:read_csv

まず CSV ファイルを DataFrame として読み込みます。

import pandas as pd
df = pd.read_csv('sample_data.csv')
print(df.head())
print('---')
print(df.dtypes)

出力

   顧客ID          日付 カテゴリ     売上  担当者
0   123  2024/01/05   食品  12000   田中
1   456  2024/01/07  日用品    NaN   佐藤
2   789  2024/01/10   食品   8500   田中
3  1011  2024/01/12  NaN   5200  NaN
4  1234  2024/01/15  日用品   3400   鈴木
---
顧客ID    int64
日付        str
カテゴリ      str
売上        str
担当者       str
dtype: object

出力を見ると顧客IDの先頭の0が消えるなど、いくつか意図通りにならない結果になりました。

よくある問題と対処

意図しない挙動を以下の表にまとめました。

問題内容
先頭ゼロのある顧客ID00123 など。そのまま読むと 123 になってしまう
日付が文字列2024/01/05 形式。そのままでは日付として扱えない
全角文字が売上文字列が混在
欠損値売上・カテゴリ・担当者に空欄あり

型変換

上記で取り上げた問題を解決していきます。

文字列への変換:dtype

先頭にゼロがある顧客IDをそのまま扱うには、read_csv の引数で dtype で文字列で指定します。

df = pd.read_csv(
    'sample_data.csv',
    dtype={'顧客ID': str}, # 先頭ゼロが消えるのを防ぐ
)
df['顧客ID'].head()

出力

0    00123
1    00456
2    00789
3    01011
4    01234
Name: 顧客ID, dtype: str

日付への変換:to_datetime

日付は pd.to_datetime を使うと文字列を日付型に変換できます。

df["日付"] = pd.to_datetime(df["日付"], format="%Y/%m/%d") # 日付列を日付型に変換
df["日付"].head()

出力

0   2024-01-05
1   2024-01-07
2   2024-01-10
3   2024-01-12
4   2024-01-15
Name: 日付, dtype: datetime64[us]

変換後は .dt アクセサで年・月・日・曜日を取り出せます。

print(df["日付"].dt.year.head())
print(df["日付"].dt.month.head())
print(df["日付"].dt.day.head())
print(df["日付"].dt.day_name().head())

出力

0    2024
1    2024
2    2024
3    2024
4    2024
Name: 日付, dtype: int32
0    1
1    1
2    1
3    1
4    1
Name: 日付, dtype: int32
0     5
1     7
2    10
3    12
4    15
Name: 日付, dtype: int32
0       Friday
1       Sunday
2    Wednesday
3       Friday
4       Monday
Name: 日付, dtype: str

数値への変換:to_numeric

売上は pd.to_numeric を使って数値に変換しますが、まずは全角文字を半角に変換するために str.maketrans を使用します。

ZENKAKU = str.maketrans('0123456789', '0123456789')
df['売上'] = df['売上'].str.translate(ZENKAKU)
print(df['売上'].head())
print('---')
df['売上'] = pd.to_numeric(df['売上'])
print(df['売上'].head())

出力

0    12000
1      NaN
2     8500
3     5200
4     3400
Name: 売上, dtype: str
---
0    12000.0
1        NaN
2     8500.0
3     5200.0
4     3400.0
Name: 売上, dtype: float64

欠損処理:dropna/ fillna

欠損値(NaN)を放置すると集計結果がずれたりエラーになったりします。  

方針は「削除するか埋めるか」の二択です。

欠損の確認

まず欠損の状況を把握してから方針を決めます。

print(df.isnull().sum()) # 列ごとの欠損件数
顧客ID    0
日付      0
カテゴリ    1
売上      4
担当者     3
dtype: int64

削除:dropna

欠損を含む行ごと取り除く場合は dropna を使用しますが、重要な列だけを subset で指定するのが安全です。

print(df.dropna().head()) # 1つでも NaN がある行を削除(カテゴリ、売上、担当者)
print('---')
print(df.dropna(subset=['売上']).head()) # 特定の列に NaN がある行だけ削除

出力

print(df.dropna().head()) # 1つでも NaN がある行を削除(カテゴリ、売上、担当者)
print('---')
print(df.dropna(subset=['売上']).head()) # 特定の列に NaN がある行だけ削除

穴埋め:fillna

欠損を特定の値で置き換える場合は、fillna で欠損が分かるような0や不明、もしくは統計に影響が小さい平均値を埋めます。

print(df["売上"].fillna(0)) # 数値列は 0 で埋める
print('---')
print(df["カテゴリ"].fillna("不明")) # 文字列列は固定値で埋める
print('---')
print(df["売上"].fillna(df["売上"].mean())) # 平均値で埋める

出力

0     12000.0
1         0.0
2      8500.0
3      5200.0
4      3400.0
5     15000.0
6         0.0
7      9800.0
8     42000.0
9         0.0
10    88000.0
11     1200.0
12        0.0
13    67000.0
14     4300.0
Name: 売上, dtype: float64
---
0      食品
1     日用品
2      食品
3      不明
4     日用品
5      食品
6      家電
7     日用品
8      家電
9      食品
10     家電
11    日用品
12     食品
13     家電
14     食品
Name: カテゴリ, dtype: str
---
0     12000.000000
1     23309.090909
2      8500.000000
3      5200.000000
4      3400.000000
5     15000.000000
6     23309.090909
7      9800.000000
8     42000.000000
9     23309.090909
10    88000.000000
11     1200.000000
12    23309.090909
13    67000.000000
14     4300.000000
Name: 売上, dtype: float64

まとめ:前処理の基本の流れ

この記事ではデータ読み込みから前処理までを紹介しました。
最後に記事内で使用したコードと前処理後データをまとめました。

import pandas as pd

# 1. 読み込み
df = pd.read_csv('sample_data.csv', dtype={'顧客ID': str})

# 2. 型変換
df["日付"] = pd.to_datetime(df["日付"], format="%Y/%m/%d")
#
ZENKAKU = str.maketrans('0123456789', '0123456789')
df['売上'] = df['売上'].str.translate(ZENKAKU)
df['売上'] = pd.to_numeric(df['売上'])

# 3. 欠損処理
df['カテゴリ'] = df['カテゴリ'].fillna('不明')
df['売上'] = df['売上'].fillna(df['売上'].mean())
df['担当者'] = df['担当者'].fillna('不明')

# 4. 確認
print(df.dtypes)
print('---')
print(df.isnull().sum())
顧客ID,日付,カテゴリ,売上,担当者
00123,2024-01-05,食品,12000.0,田中
00456,2024-01-07,日用品,23309.090909090908,佐藤
00789,2024-01-10,食品,8500.0,田中
01011,2024-01-12,不明,5200.0,不明
01234,2024-01-15,日用品,3400.0,鈴木
01567,2024-01-18,食品,15000.0,佐藤
01890,2024-01-20,家電,23309.090909090908,田中
02112,2024-01-22,日用品,9800.0,不明
02345,2024-01-25,家電,42000.0,鈴木
02678,2024-01-28,食品,23309.090909090908,田中
02901,2024-02-01,家電,88000.0,佐藤
03124,2024-02-03,日用品,1200.0,鈴木
03457,2024-02-05,食品,23309.090909090908,不明
03680,2024-02-08,家電,67000.0,田中
03903,2024-02-10,食品,4300.0,佐藤

データが整ったら集計に進みましょう

コメント

タイトルとURLをコピーしました