
想定状況
他の人に入力してもらったデータ(整数、と信じたい…)を良く見たら、半角、全角の乱れ舞、スペースが入っていたりいなかったり…。そこでこれを Pythonic data cleaning っぽく解決したい、という想定です(Script 1)。
ざっと入力を見たところではこんな感じ。
- 全角数字・カンマが混在している。
- カンマ が半角だったり全角だったり、はたまた読点だったり。
- 気分で Space が入ってたり、なかったり。
- 漢数字が混ざっている。どこの国に外注したの⁉️ 😱
- アンケート選択肢のまとめなのに、数字に重複がある。
Script 1 こんなデータを入手した 😭 という想定
raw_str = '3, 壱, 4, 1,⑤ ,九 ,2、 6 ,伍,3, 2'
👆これを、👇こうしたい。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import unicodedata
def num_cleaner(input_string):
"""Messy string data to a set of integers"""
nml_str = unicodedata.normalize('NFKC', input_string)
nml_list = re.split(r'[\u002C\u3001]', nml_str)
nos_list = [i.strip() for i in nml_list]
int_list = [int(unicodedata.numeric(i)) for i in nos_list]
int_set = set(int_list)
return int_set
if __name__ == '__main__':
raw_str = '3, 壱, 4, 1,⑤ ,九 ,2、 6 ,伍,3, 2'
print(f'Raw string: {raw_str}')
print(f'Clean list: {num_cleaner(raw_str)}')
Raw string: 3, 壱, 4, 1,⑤ ,九 ,2、 6 ,伍,3, 2
Clean list: {1, 2, 3, 4, 5, 6, 9}
全体の流れ
こんな感じで片付けようと思います。
- 全角文字列を半角文字列に変換する(Script 2)
- list 化して個々の要素に分ける(Script 3)。
- 余分なスペースを全部削除する(Script 4)。
- 漢数字を含め、要素を整数(int)に翻訳する(Script 5)。
- 重複した数値を削除する(Script 6)。
- ビールの栓を抜く 🍻
それでは始めます
冷蔵庫にビールを格納したところで、作業開始。
全角文字列を半角文字列に変換
テキストを正規化する関数はもちろん Python にもあります。その名も unicodedata.normalize(form, unistr) ❗️
どんなふうに正規化するかを指定するパラメータ(form)には、 NFC、NFKC、NFD、NFKD の 4 種類がありますが、迷ったら NFKC を使っておけば大体ダイジョーブみたいです。
もちろん、データの状況や欲しい結果によって NFKC じゃダメな場合もあるかもしれませんが、Cult Occult Coding™ 的には難しいことを言われても分からないので、そんなときはとりあえず、他の 3 種類も含めて全部試して気に入ったのを選ぶと 🤗
Script 2 全角文字列を半角文字列に変換
import unicodedata
raw_str = '3, 壱, 4, 1,⑤ ,九 ,2、 6 ,伍,3, 2'
nml_str = unicodedata.normalize('NFKC', raw_str)
print('Normalized string:', nml_str)
Normalized string: 3, 壱, 4, 1,5 ,九 ,2、 6 ,伍,3, 2
(*1) 実は、まだ漢字だとか全角スペースが残っているので「半角文字列」ではありませんが、それは見なかったことに 😅
list 化して個々の数値に分ける
この時点でまだデータは 1 本の文字列なので、要素に切り分けます。
切り分けの目印(separator)となるカンマの種類もなんか色々混ざっています。そこで、 str.split(sep=None, maxsplit=-1) を「色々回」だけ繰り返してササっと list にすればおしまい。
と思いきや、1 回目に list にしちゃうと、list に split は掛けられないので、2 回目はまた str に戻さなきゃダメ? 更に 3 回目とか。いや、これはもしかしたらちょっと大変かも…。
と思っていたら、実は Built-in で re.split(pattern, string, maxsplit=0, flags=0) というのがありました 💕
これなら regex 1 回で片付きます。
regex の [] の中身は、混在している半角カンマ(,)全角カンマ(,)、そして全角読点(、)に対応して [,,、] としても問題ありませんが、中に何が書いてあるのかイマイチわかりにくいので、 Unicode point で表現しておきました(*2)。
しかし見た目的には ,,、の方がかわいいかもしれません 🐙
(*2) Unicode の FULLWIDTH COMMA は、全角カンマ(,)と全角読点(、)の両方を含んでいるので、ふたついっぺんに片付きます。
Script 3 list 化して個々の要素に分ける
import re
# Unicode points for the COMMA and the FULLWIDTH COMMA are
# \u0020 and \u3001, respectively.
nml_list = re.split(r'[\u002C\u3001]', nml_str)
print('Normalized list:', nml_list)
Normalized list: ['3', ' 壱', ' 4', ' 1', '5 ', '九 ', '2', ' 6 ', '伍', '3', ' 2']
余分なスペースを全部削除
list の要素には、まだ気ままに挿入されたスペースが残っているのでこれを削除します。
Script 4 余分なスペースを全部削除
nos_list = []
for i in nml_list:
no_space_element = i.strip()
nos_list.append(no_space_element)
print('No-spaces list:', nos_list)
No-spaces list: ['3', '壱', '4', '1', '5', '九', '2', '6', '伍', '3', '2']
漢数字を整数(int)に翻訳
やっとデータっぽくなって来たので、いよいよ漢数字を含むすべての文字を整数に変換します。
実は、漢数字→数値(float)は Built-in の unicodedata.numeric(chr[, default]) を使えば一発で片付きます 👍👍👍
Script 5 漢数字を整数(int)に翻訳
int_list = []
for chr in nos_list:
num_flt = unicodedata.numeric(chr) # Translate to numeric value (float)
num_int = int(num_flt) # Convert to integer
int_list.append(num_int)
print('Integer list:', int_list)
Integer list: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 2]
重複した数値を削除
一部の数値がダブっているので、unique 化します。これは set() 関数で解決。
Script 6 重複した数値を削除
int_set = set(int_list)
print('Integer set:', int_set)
Integer set: {1, 2, 3, 4, 5, 6, 9}
おまけ
ときおり悩みの種になるタイ数字も行ける!大きな桁も大丈夫(ただし京以上は未対応)。よっしゃ、九蓮宝燈だ!(正解はこちらで実験 😸)
Script 7 Unicode すげぇ!
thai_digit = '๓,๑,๔,๑,๕,๙,๒,๖,๕,๓,๒'
powers = '一,十,百,千,万,億,兆'
chinese_digit = '🀇,🀇,🀇,🀏,🀏,🀏,🀐,🀐,🀐,🀘,🀘,🀘,🀙,🀙'
print('Thai:', num_cleaner(thai_digit))
print('Powers:', num_cleaner(powers))
print('Chinese:', num_cleaner(chinese_digit))
🍺 Enjoy!