今回は、pandasのread_csvメソッドを使用して、GoogleAdSenseレポートで取得したExcel用CSVファイルを読み込んでいきたいと思います。
なぜこのような記事を書くに至ったかというと、データの形式、つまり文字コードに乏しかったため「UnicodeDecodeError」に随分時間を掛けてしまったからです。
文字コードと一口に言っても色々な種類があり複雑です。
簡単にいってしまうと、プログラムが理解できるように各文字(「a」や「あ」)に与えられた番号です。
逆に、機械の根本は番号によって処理されているので、それを人間が理解できるように「この番号ならばこの文字だ」といったように変換されています。
その変換器を文字コードと言い、代表的なものに「ASCII」や「Unicode」があります。
ちなみにPythonでサポートされている文字コードは、Unicode標準で、Unicode用の符号化方式は8ビットのマルチバイトとなっています。
Unicode用の符号化方式で8ビットを使うならばUTF-8(Unicode Transformation Format-8)、16ビットならばUTF-16と定義されています。
Unicode自体は国際標準規格となっており、UTF-8は多くのプログラムで使われていると言われています。
「バイト」と言うのは各文字に充てられたメモリ単位のことで、UTF-8やUTF-16では文字の種類によってバイト数があります。
特定の文字を1~4バイトと表現できることから(Wikipediaより)、マルチバイト、つまり可変長の文字符号化方式となります。
詳しくは割愛してしまいますが、作成されたファイル内の文字コードはプログラムによって形式が違うので、外部ファイルを読み込んだりする際は作成されたファイルの形式(文字コード)に合わせて作業場に落とし込まないといけません。
なので今回は文字化けやUnicodeDecodeエラーなどを回避できるように、未知のファイル形式をスムーズに読み込み、そしてExcelなどでも日本語文字化けを防ぐ書き込みなどを実装していきます。
実行環境 |
---|
Windows Subsystem for Linux |
Python 3.6.9 |
pip 9.0.1 |
jupyter notebook |
使用ライブラリ | ライセンス |
---|---|
pandas==1.1.2 | BSD |
ここでは題名にもあるように、GoogleAdSenseレポートから「Excel用CSVファイル」をエクスポートします。
そして実行環境はLinuxから立ち上げたJupyterNotebookです。
取得したCSVファイルの中身を確認してみますが、JupyterNotebookでLinuxコマンドを実行する際は「!」を先頭に置きます。
通常CSVファイルの中身は、以下のようにカンマ「,」やタブ(スペース)「\t」で区切られているとイメージしましたが、変換できない文字列などは「?」により置き換えられています。
UTF-8で作成されたCSVファイルの中身(イメージ)
'列名1','列名2','列名3'
'2019-10-15',61,5
'2019-10-16',174,47
...
この時点ではExcel用CSVファイルとしてエクスポートしてきたわけですから、恐らく「Shift-JIS」やShift-JISの拡張版「cp932」の文字コードで読み込めばいいだろうという予想です。
ではPandasのread_csvメソッドを使用して、各文字コードで読み込んでみたいと思います。
失敗例から実行するので、興味の無い方は飛ばしてください。
まずはデフォルトの「UTF-8」でCSVファイルを読み込んだ例
import pandas as pd
df = pd.read_csv('report.csv')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
デコードした結果、無効な開始バイト「'0xff'」が検出されました。
無効な開始バイト「'0xff'」はプレフィックスが「'0x'」なので16進数として表現されていますが、ある種の文字列です。
Python(3)は文字を「UTF-8」でバイト列から文字列に戻す(デコード)ので、元のファイル内の文字コードが「UTF-8」と異なる文字コードであった場合、無効、つまりデコードできなくなります。
因みに、文字列からバイト列に変換することを、エンコードと言います。
殆どのファイル読み込みの「UnidodeDecodeError」は異なる文字コードによるエンコード・デコードだと思われます。
では次に進んでみます。
「Shift-JIS」でCSVファイルを読み込んだ例
Pandasのread_csvメソッドやPython組み込み関数のopenメソッドはデフォルトの「UTF-8」としてファイル内のデータをデコードしようとするので、パラメータの「encoding」で文字コードを指定します。
import pandas as pd
df = pd.read_csv('report.csv', encoding='shift-jis')
UnicodeDecodeError: 'shift_jis' codec can't decode byte 0xff in position 0: illegal multibyte sequence
Shit-JISで読み込もうとしたファイルも、無効なバイト列「'0xff'」が検出されました。
「cp932」はファイル読み込み時のエンコーディング指定で、「Shift-JIS」にした結果文字化けやエラーを引き起こした際に試される文字コードです。
と言っても、Shift-JISの拡張版で日本語に幅広く対応した文字コードとなっています。
「cp932」でCSVファイルを読み込んだ例
import pandas as pd
df = pd.read_csv('report.csv', encoding='cp932')
UnicodeDecodeError: 'cp932' codec can't decode byte 0xfc in position 12: illegal multibyte sequence
今度はこれまでとは違う無効なバイト「'0xfc'」を検出しました。
これまでは各文字コードによるエラー例とエラーの詳細について少しだけ触れました。
CSVファイルを躊躇なく読み込んでいましたが、この記事を書くに当たり未知のファイルを扱う際は、どのようなデータ形式なのかを確認してから、プログラムに流すという順番が健全だったと痛感しています。
ここからは、文字化けやエラー等を回避するためにファイルを読み込む前のデータ形式確認、そして適切な文字コードを指定して、余計なエスケープシーケンスなどを取り除いていきたいと思います。
実装はLinuxコマンドを使用していくため、Windowsの方は「cmd ファイル 形式 確認」と検索するとヒットすると思います。
Linuxでファイルのデータ形式を確認するには、「file」コマンドを使用します。
オプションは幾つかありますが、引数にファイル名を指定するだけで簡単な詳細を取得できます。
$ file report.csv
report.csv: Little-endian UTF-16 Unicode text
なんとGoogleAdSenseレポートからエクスポートしたExcel用CSVファイルは「UTF-16LE」形式のデータでした。
「UTF-16」にはビッグエンディアン(UTF-16BE)とリトルエンディアン(UTF-16LE)があり、双方16進数の桁数により格納される順番が異なります。
詳しい事は割愛しますが、ファイルのデータ形式を確認した結果、「UTF-16」でデコードを行えば中身を取得できることが分かりました。
ではさっそく、read_csvメソッドのパラメータ「encodeing」に確認した文字コード「UTF-16」を指定して中身を取得していきます。
import pandas as pd
df = pd.read_csv('report.csv', encoding='utf-16')
df.head()
上手くデコードしてくることはできましたが、余計なエスケープシーケンス(「\t」)が紛れ込んでいます。
このエスケープシーケンスの「\t」はテキストの位置を決めるタグで、各要素の間に記述されています。
catコマンドでファイルの中身を確認してみると、数値で記述されている要素が等間隔となっています。
「\t」をカンマ「,」に置き換えることができれば、Pandasは各要素を区切ったデータフレームにしてくれます。
データ内の余分な要素を置き換えるには、read_csvメソッドのパラメータ「sep」を使用して、与えられた要素を起点に分割できます。
Python組み込み関数の「split」と同じです。
import pandas as pd
df = pd.read_csv('report.csv', encoding='utf-16', sep='\t')
df.head()
上手く読み込む事ができました。
あとは好きなように前処理を行っていくだけです。
因みにデータを書き込む際は、to_csvメソッドのパラメータ「encoding」に目的の文字コードを指定すれば、目的のプログラムによって問題無く開けるようになります。
import pandas as pd
df = pd.read_csv('report.csv', encoding='utf-16', sep='\t')
df.to_csv('report_shift_jis.csv', encoding='shift-jis')
open関数の引数に文字コードを指定して読み込み、分割したい要素はsplit関数で指定します。
import csv
with open('report.csv', mode='rt', encoding='utf-16') as file:
reader = csv.reader(file)
report = [row[0].split('\t') for row in reader]
print(report)
今回は私が時間を掛けてしまった「UnicodeDecodeError」について書いていきました。
機械語と人間後の変換プロセスは非常に複雑なため中途半端な説明で終わってしまいましたが、未知のファイルに対しての取得・変換は理解できたかと思います。
人工知能が流行っている昨今、ますますデータの活用が行われるので余力のある方はこの機会にマスターしてしまいましょう!
それでは以上となります。
最後までご覧いただきありがとうございました。