はじめに
「あれ?データが重複してる?」
「同じ意味のはずなのに、別の項目として集計されてる…」
データ分析をしていると、このような表記ゆれの問題に悩まされることがよくあります。特に、人間が入力したデータは、全角・半角の違い、微妙なスペルの違い、送り仮名の有無など、様々なゆらぎが発生しがちです。
そこで今回は、Pythonのデータ分析ライブラリであるpandasを使って、効率的に表記ゆれを対策する方法を解説します。
初心者の方でも理解しやすいように、具体例を交えながら丁寧に説明していきますので、ぜひ最後までお付き合いください!
表記ゆれって具体的にどんな問題?
表記ゆれは、データ分析の様々な場面で問題を引き起こします。例えば、以下のような状況です。
- アンケートデータの自由記述欄に「東京都」「東京」「とうきょうと」のように、同じ住所が異なる表記で入力されている。
- 商品データに「コーヒー」「珈琲」といった、同じものを指す別の名称が存在する。
- 顧客データに「株式会社A」「(株)A」「(株)A」といった、正式名称と略称が混在している。
- 日付データに「2023/12/1」「2023-12-01」「2023年12月1日」など複数のフォーマットが使われている。
- 数値データに「1,000」「1000」「1,000.00」のようにカンマや小数点の有無が統一されていない。
これらの表記ゆれを放置すると、以下のような問題が発生します。
- 正確なデータ集計ができなくなる。
- 例:「東京都」と「東京」が別の地域として集計されてしまう。
- データの結合(マージ)がうまくいかない。
- 例:「株式会社A」と「(株)A」が別の企業として扱われ、顧客IDをキーにしたデータ結合ができない。
- 機械学習モデルの精度が低下する。
- 例:同じ商品なのに「コーヒー」と「珈琲」で異なる特徴量として学習されてしまい、予測精度が下がる。
pandasで表記ゆれを対策するメリット
pandasを使って表記ゆれ対策をするメリットは、主に以下の3つです。
- 効率的: 大量のデータを一括処理できる。
- 柔軟: 様々なパターンに対応できる。
- 再現性: 処理手順をコードとして記録できる。
準備
まずは、pandasをインポートします。
まだインストールしていない場合は、pip install pandas
でインストールしてください。
import pandas as pd
また、今回はサンプルデータとして、以下のようなCSVファイルを読み込んで使います。
商品名,価格,カテゴリ
コーヒー ,300,飲み物
珈琲,350,飲み物
紅茶 ,400,飲み物
緑茶,380,飲み物
コーヒー牛乳,150,飲み物
このデータをsample_data.csv
というファイル名で保存し、以下のコードを実行します。
# サンプルデータを読み込む
df = pd.read_csv("sample_data.csv", encoding="utf-8") # Shift-JISの場合は「shift-jis」で読み込む
print(df)
以下のように、マークダウンの表形式で出力されます。
商品名 | 価格 | カテゴリ | |
---|---|---|---|
0 | コーヒー | 300 | 飲み物 |
1 | 珈琲 | 350 | 飲み物 |
2 | 紅茶 | 400 | 飲み物 |
3 | 緑茶 | 380 | 飲み物 |
4 | コーヒー牛乳 | 150 | 飲み物 |
よく見ると、「コーヒー」と「珈琲」という表記ゆれや、商品名の前後に余計な空白があることがわかりますね。
実践!pandasを使った表記ゆれ対策
ここからは、具体的な表記ゆれのパターン別に対策方法を見ていきましょう。
1. 全角・半角の統一
まずは、全角・半角のゆれを解消する方法です。str.replace()
メソッドを使い、特定の文字を置換します。
例:全角スペースを半角スペースに統一
# 「カテゴリ」列の全角スペースを半角スペースに統一
df['カテゴリ'] = df['カテゴリ'].str.replace(' ', ' ')
print(df)
商品名 | 価格 | カテゴリ | |
---|---|---|---|
0 | コーヒー | 300 | 飲み物 |
1 | 珈琲 | 350 | 飲み物 |
2 | 紅茶 | 400 | 飲み物 |
3 | 緑茶 | 380 | 飲み物 |
4 | コーヒー牛乳 | 150 | 飲み物 |
「カテゴリ」列の中の全角スペース「 」が半角スペース「 」に変換されました。
全角英数字を半角に統一する
import pandas as pd
# サンプルデータ
data = {'name': ['docomo', 'ソフトバンク株式会社', 'auショップ', 'apple']}
df_zenkaku = pd.DataFrame(data)
# 全角英数字を半角に変換
df_zenkaku['name'] = df_zenkaku['name'].str.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))
print(df_zenkaku)
name | |
---|---|
0 | docomo |
1 | ソフトバンク株式会社 |
2 | auショップ |
3 | apple |
str.maketrans
とstr.translate
を組み合わせて、全角英数字を半角に変換しています。
2. 不要な空白の削除
文字列の前後に余計な空白があると、別の文字列として認識されてしまいます。str.strip()
メソッドで、前後の空白を削除しましょう。
例:商品名の前後の空白を削除
# 「商品名」列の前後の空白を削除
df['商品名'] = df['商品名'].str.strip()
print(df)
商品名 | 価格 | カテゴリ | |
---|---|---|---|
0 | コーヒー | 300 | 飲み物 |
1 | 珈琲 | 350 | 飲み物 |
2 | 紅茶 | 400 | 飲み物 |
3 | 緑茶 | 380 | 飲み物 |
4 | コーヒー牛乳 | 150 | 飲み物 |
「商品名」列の前後の空白が削除されました。
文字列の途中の空白を削除する
# 「商品名」列の中間にある空白を削除
df['商品名'] = df['商品名'].str.replace(' ', '')
print(df)
商品名 | 価格 | カテゴリ | |
---|---|---|---|
0 | コーヒー | 300 | 飲み物 |
1 | 珈琲 | 350 | 飲み物 |
2 | 紅茶 | 400 | 飲み物 |
3 | 緑茶 | 380 | 飲み物 |
4 | コーヒー牛乳 | 150 | 飲み物 |
str.replace(' ', '')
で、文字列の中間にある空白も削除できます。
3. 特定の文字列の置換
特定の文字列を別の文字列に置き換えることで、表記を統一できます。
例:「珈琲」を「コーヒー」に統一
# 「商品名」列の「珈琲」を「コーヒー」に置換
df['商品名'] = df['商品名'].str.replace('珈琲', 'コーヒー')
print(df)
商品名 | 価格 | カテゴリ | |
---|---|---|---|
0 | コーヒー | 300 | 飲み物 |
1 | コーヒー | 350 | 飲み物 |
2 | 紅茶 | 400 | 飲み物 |
3 | 緑茶 | 380 | 飲み物 |
4 | コーヒー牛乳 | 150 | 飲み物 |
「商品名」列の「珈琲」が「コーヒー」に変更されました。
例:「株式会社」を「(株)」に統一
# 株式会社を含むサンプルデータ
data = {'企業名': ['株式会社ABC', 'XYZ株式会社', '株式会社あいうえお']}
df_company = pd.DataFrame(data)
# 「株式会社」を「(株)」に置換
df_company['企業名'] = df_company['企業名'].str.replace('株式会社', '(株)')
print(df_company)
企業名 | |
---|---|
0 | (株)ABC |
1 | XYZ(株) |
2 | (株)あいうえお |
「株式会社」が前や後ろに付いていても、まとめて「(株)」に置換できます。
4. 正規表現を使った高度な置換
正規表現を使うと、より複雑なパターンや曖昧な表記ゆれにも対応できます。
例:カッコの違いを統一
# カッコの表記ゆれを統一するデータフレームを作成
df_かっこ = pd.DataFrame({
'会社名': ['株式会社A', '(株)A', '(株)A', 'A株式会社', 'A(株)', 'A(株)']
})
# カッコのパターンを正規表現で定義し、置換
df_かっこ['会社名'] = df_かっこ['会社名'].str.replace(r'[(\(]株[)\)]', '(株)', regex=True)
print(df_かっこ)
会社名 | |
---|---|
0 | 株式会社A |
1 | (株)A |
2 | (株)A |
3 | A株式会社 |
4 | A(株) |
5 | A(株) |
正規表現 r'[(\(]株[)\)]'
は、全角の「(」または半角の「(」で始まり、「株」を挟んで、全角の「)」または半角の「)」で終わる文字列にマッチします。 「(株)」「(株)」「(株」「株)」など、様々なパターンを「(株)」に統一できました。regex=True
を指定することで正規表現による置換が可能となります。
例:日付フォーマットの統一
# 日付の表記ゆれを含むサンプルデータ
data = {'日付': ['2023/12/1', '2023-12-01', '2023年12月1日', '12/1/2023', 'R5.12.1']}
df_date = pd.DataFrame(data)
# 正規表現でYYYY-MM-DD形式に統一
df_date['日付'] = df_date['日付'].str.replace(r'(\d{4})[/-](\d{1,2})[/-](\d{1,2})', r'\1-\2-\3', regex=True)
df_date['日付'] = df_date['日付'].str.replace(r'(\d{4})年(\d{1,2})月(\d{1,2})日', r'\1-\2-\3', regex=True)
df_date['日付'] = df_date['日付'].str.replace(r'(\d{1,2})[/-](\d{1,2})[/-](\d{4})', r'\3-\1-\2', regex=True)
df_date['日付'] = df_date['日付'].str.replace(r'R(\d{1,2})\.(\d{1,2})\.(\d{1,2})', lambda x: str(int(x.group(1)) + 2018) + '-' + x.group(2) + '-' + x.group(3), regex=True)
print(df_date)
日付 | |
---|---|
0 | 2023-12-1 |
1 | 2023-12-01 |
2 | 2023-12-1 |
3 | 2023-12-1 |
4 | 2023-12-1 |
正規表現を駆使して、様々な日付フォーマットを「YYYY-MM-DD」形式に統一しました。西暦と和暦の混在にも対応しています。
参考:datetime
モジュールを使った日付フォーマットの統一
以下は、Pythonの標準ライブラリであるdatetime
モジュールを使って、日付フォーマットを統一する方法です。
import pandas as pd
from datetime import datetime
def convert_date_format_with_datetime(date_str):
formats = [
"%Y/%m/%d",
"%Y-%m-%d",
"%Y年%m月%d日",
"%m/%d/%Y",
"R%y.%m.%d"
]
for fmt in formats:
try:
if fmt == "R%y.%m.%d":
dt = datetime.strptime(date_str[1:], "%y.%m.%d")
year = dt.year + (2018 - 4)
return f"{year:04}-{dt.month:02}-{dt.day:02}"
else:
dt = datetime.strptime(date_str, fmt)
return dt.strftime("%Y-%m-%d")
except ValueError:
pass
return date_str
# 日付の表記ゆれを含むサンプルデータ
data = {'日付': ['2023/12/1', '2023-12-01', '2023年12月1日', '12/1/2023', 'R5.12.1']}
df_date = pd.DataFrame(data)
# 日付フォーマットを変換
df_date['日付'] = df_date['日付'].apply(convert_date_format_with_datetime)
print(df_date)
日付 | |
---|---|
0 | 2023-12-01 |
1 | 2023-12-01 |
2 | 2023-12-01 |
3 | 2023-12-01 |
4 | 2023-12-01 |
この方法では、formats
リストに定義された複数のフォーマット文字列を使って、datetime.strptime
で日付文字列の解析を試みます。解析に成功したら、datetime
オブジェクトをstrftime
で指定の”YYYY-MM-DD”形式に変換します。
正規表現を使う方法と比べると、datetime
モジュールを使う方法は、日付として妥当かどうかの検証も同時に行える利点があります。また、strptime
とstrftime
でサポートされている多様なフォーマットに対応できることも強みです。一方で、正規表現ほど柔軟なパターンマッチングは難しいため、複雑なパターンや非常に曖昧な表記ゆれには対応しきれない場合があります。
どちらの方法にも一長一短があるため、扱うデータの特性や要件に応じて適切な方法を選択することが重要です。
5. 名寄せ辞書の活用
よく使う名称のリストがある場合は、名寄せ辞書を作成しておくと便利です。
例:商品名の名寄せ
# 名寄せ辞書を作成
name_dict = {
'珈琲': 'コーヒー',
'カフェラテ': 'コーヒー',
'アイスコーヒー': 'コーヒー',
'ブレンドコーヒー': 'コーヒー',
'紅茶': '紅茶',
'緑茶': '緑茶',
'抹茶': '緑茶'
}
# 名寄せ辞書を使って「商品名」列を置換
df['商品名'] = df['商品名'].replace(name_dict)
print(df)
商品名 | 価格 | カテゴリ | |
---|---|---|---|
0 | コーヒー | 300 | 飲み物 |
1 | コーヒー | 350 | 飲み物 |
2 | 紅茶 | 400 | 飲み物 |
3 | 緑茶 | 380 | 飲み物 |
4 | コーヒー牛乳 | 150 | 飲み物 |
名寄せ辞書name_dict
を定義し、replace()
メソッドで適用することで、より柔軟な名寄せ処理が可能になります。
「商品名」列の「珈琲」が、名寄せ辞書によって「コーヒー」に置換されていることが分かります。
例:都道府県名の名寄せ
import pandas as pd
# 都道府県名の名寄せ辞書を作成(正式名称に統一)
pref_dict = {
'東京': '東京都',
'京都': '京都府',
'大阪': '大阪府',
'北海道': '北海道',
'沖縄': '沖縄県'
}
# サンプルデータ(都道府県名を省略した形式)
data = {'住所': ['東京渋谷区', '京都大阪市', '北海道札幌市', '沖縄那覇市']}
df_pref = pd.DataFrame(data)
# 名寄せ辞書を使って「住所」列を置換
df_pref['住所'] = df_pref['住所'].replace(pref_dict, regex=True)
print(df_pref)
出力結果
住所 | |
---|---|
0 | 東京都渋谷区 |
1 | 京都府大阪市 |
2 | 北海道札幌市 |
3 | 沖縄県那覇市 |
説明
- 名寄せ辞書
pref_dict
: 都道府県名をキーとし、正式名称を値としています。これにより、「東京」は「東京都」、「京都」は「京都府」のように、省略された都道府県名が正式名称に変換されます。 - サンプルデータ
data
: 元のコードで提示されていた、都道府県名が省略されている、または「都」「道」「府」「県」が付けられていない形式の住所データです。 - 処理:
df_pref['住所'].replace(pref_dict, regex=True)
の部分で、名寄せ辞書を用いて「住所」列の都道府県名を置換しています。regex=True
により、部分一致で置換が行われます。 - 出力結果: 各行の「住所」列において、省略されていた都道府県名が正式名称で補完されています。
このコードにより、都道府県名が「都」「道」「府」「県」付きの正式名称で統一され、一貫性のあるデータが得られます。
まとめ
今回は、pandasを使った表記ゆれ対策について解説しました。
- 全角・半角の統一:
str.replace()
、str.maketrans()
、str.translate()
- 不要な空白の削除:
str.strip()
、str.replace()
- 特定の文字列の置換:
str.replace()
- 正規表現を使った高度な置換:
str.replace()
と正規表現の組み合わせ - 名寄せ辞書の活用:
replace()
と辞書の組み合わせ
これらの方法を組み合わせることで、データ分析における表記ゆれの問題を効率的に解決できます。
表記ゆれ対策は、データ分析の前処理において非常に重要なステップです。
今回紹介したテクニックを活用して、データクレンジングのスキルを向上させ、より正確で信頼性の高い分析結果を得られるようにしましょう!
おまけ:表記ゆれ修正の取り消し
データ分析の前処理で表記ゆれに対応した後に元の表記に戻す必要が生じる場合があります。このような状況に備えて、以下のような方法で対処できます。
import pandas as pd
# 1. 処理前のデータをバックアップする
df_original = pd.read_csv("sample_data.csv", encoding="utf-8")
df_original.to_csv("sample_data_backup.csv", encoding="utf-8", index=False)
# 2. 表記ゆれの修正を行う(前述の処理)
name_dict = {
'珈琲': 'コーヒー',
'カフェラテ': 'コーヒー',
'アイスコーヒー': 'コーヒー',
'ブレンドコーヒー': 'コーヒー',
'紅茶': '紅茶',
'緑茶': '緑茶',
'抹茶': '緑茶'
}
df_modified = df_original.copy()
df_modified['商品名'] = df_modified['商品名'].replace(name_dict)
# 3. 元の表記に戻す必要が生じた場合
# 方法1: バックアップから直接復元する
df_restored = pd.read_csv("sample_data_backup.csv", encoding="utf-8")
# 方法2: 名寄せ辞書を反転させて逆変換する
reversed_name_dict = {v: k for k, v in name_dict.items()}
df_reversed = df_modified.copy()
df_reversed['商品名'] = df_reversed['商品名'].replace(reversed_name_dict)
# 結果の確認
print("元のデータ:")
print(df_original)
print("\n表記ゆれ修正後:")
print(df_modified)
print("\nバックアップから復元:")
print(df_restored)
print("\n逆変換による復元:")
print(df_reversed)
この方法には以下の注意点があります。
バックアップからの復元(方法1)
- 最も確実な方法です
- 表記ゆれ修正前の状態を完全に保持できます
- ストレージ容量が必要になります
逆変換による復元(方法2)
- 追加のストレージが不要です
- 一対多の変換を行った場合、完全な復元が難しい場合があります
- 例:’コーヒー’に統一された複数の表記(’珈琲’、’カフェラテ’など)は、どの表記に戻すべきか判断できません
したがって、重要なデータを扱う場合は、必ず処理前のデータをバックアップしておくことを推奨します。
コメント