想定状況
他の人に入力してもらったデータ(整数、と信じたい…)を良く見たら、半角、全角の乱れ舞、スペースが入っていたりいなかったり…。そこでこれを 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!