3.7. 参考:日本語の取り扱いと正規表現#

3.7.1. 日本語を含むデータを可視化する方法#

日本語をすぐに表示させたい場合は、便利なモジュールが公開されているので、そちらを使うのが簡単です。

  • requirements.txtを用いたパッケージ類のダウンロードが済んでいない場合は、次のcellの1行目のコメントアウトを外してモジュールをインストールしてから実行してください。

# !pip install japanize-matplotlib #モジュールのインストール
import japanize_matplotlib
import matplotlib.pyplot as plt
import pandas as pd
years = [1960, 1970, 1980, 1990, 2000, 2010, 2020]
life_expectancy= [67.6, 71.9, 76.0, 78.8, 81.0, 82.8, 84.6]

plt.plot(years, life_expectancy, color = 'blue', marker = 'o', linestyle = 'solid')
plt.title('平均寿命')

plt.ylabel('歳')
plt.show()
../../_images/ccf8aee4ebd1cc07bd63855f026a408ebcadacaf68ed15d4deb4ed1807dca7ee.png

3.7.2. ファイル読み書きでのエンコーディングのエラーへの対応方法#

続いて日本語のテキストを含むファイルの読み書きについてのTipsを紹介します。

日本語テキストのファイルの読み書きがうまく行かない場合があるかもしれません。

例えば、以下のようなtext_dfという日本語テキストを含むテーブルデータがあったとします。

text = ['パイソン','1','分析', '科学', 
        '統計', '動的','あーる', '機械学習', '深層学習', '見識']
freq = [100, 40, 80, 21, 10, 3, 45, 79, 81, 3]
importance = [200, 180, 190, 50, 150, 30, 20, 40, 20, 90]

text_df = pd.DataFrame({'text':text, 'freq': freq, 'importance': importance})
text_df
text freq importance
0 パイソン 100 200
1 40 180
2 分析 80 190
3 科学 21 50
4 統計 10 150
5 動的 3 30
6 あーる 45 20
7 機械学習 79 40
8 深層学習 81 20
9 見識 3 90

このデータを保存してみましょう。 ここでは、テストのためencodingはSHIFT-JISにしています。

text_df.to_csv('text_ja.csv', encoding='SHIFT-JIS')

このようなテーブルをエクセル等で作成すると環境によってencodingが異なりPythonでひらけない場合があります。 そのような場合は、エクセルデータ作成時に文字コードを指定したり、作成後に文字コードを変更したりすることで回避できます。 まずは、読み込みたいデータの文字コードは何かを把握するようにしましょう。 日本語ファイルで多く用いられている文字コードはShift-JIS, CP932, utf-8などです。

例えば、pandasのread_csvではデフォルトではencoding=utf-8でファイルを読もうとします。そこにShift-JISなどエンコードされたファイルをそのまま読もうとするとエラーになるはずです。

text_df_r = pd.read_csv('text_ja.csv')
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
Cell In[5], line 1
----> 1 text_df_r = pd.read_csv('text_ja.csv')

File /opt/anaconda3/lib/python3.11/site-packages/pandas/io/parsers/readers.py:948, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
    935 kwds_defaults = _refine_defaults_read(
    936     dialect,
    937     delimiter,
   (...)
    944     dtype_backend=dtype_backend,
    945 )
    946 kwds.update(kwds_defaults)
--> 948 return _read(filepath_or_buffer, kwds)

File /opt/anaconda3/lib/python3.11/site-packages/pandas/io/parsers/readers.py:611, in _read(filepath_or_buffer, kwds)
    608 _validate_names(kwds.get("names", None))
    610 # Create the parser.
--> 611 parser = TextFileReader(filepath_or_buffer, **kwds)
    613 if chunksize or iterator:
    614     return parser

File /opt/anaconda3/lib/python3.11/site-packages/pandas/io/parsers/readers.py:1448, in TextFileReader.__init__(self, f, engine, **kwds)
   1445     self.options["has_index_names"] = kwds["has_index_names"]
   1447 self.handles: IOHandles | None = None
-> 1448 self._engine = self._make_engine(f, self.engine)

File /opt/anaconda3/lib/python3.11/site-packages/pandas/io/parsers/readers.py:1723, in TextFileReader._make_engine(self, f, engine)
   1720     raise ValueError(msg)
   1722 try:
-> 1723     return mapping[engine](f, **self.options)
   1724 except Exception:
   1725     if self.handles is not None:

File /opt/anaconda3/lib/python3.11/site-packages/pandas/io/parsers/c_parser_wrapper.py:93, in CParserWrapper.__init__(self, src, **kwds)
     90 if kwds["dtype_backend"] == "pyarrow":
     91     # Fail here loudly instead of in cython after reading
     92     import_optional_dependency("pyarrow")
---> 93 self._reader = parsers.TextReader(src, **kwds)
     95 self.unnamed_cols = self._reader.unnamed_cols
     97 # error: Cannot determine type of 'names'

File parsers.pyx:579, in pandas._libs.parsers.TextReader.__cinit__()

File parsers.pyx:668, in pandas._libs.parsers.TextReader._get_header()

File parsers.pyx:879, in pandas._libs.parsers.TextReader._tokenize_rows()

File parsers.pyx:890, in pandas._libs.parsers.TextReader._check_tokenize_status()

File parsers.pyx:2050, in pandas._libs.parsers.raise_parser_error()

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x83 in position 24: invalid start byte

UnicodeDecodeErrorが確認されました。 このような場合はencodingを指定して読み込んでみましょう。encodingが何かを確認してから指定しましょう。

text_df_r = pd.read_csv('text_ja.csv', index_col = 0, encoding = 'cp932')
text_df_r 
text freq importance
0 パイソン 100 200
1 40 180
2 分析 80 190
3 科学 21 50
4 統計 10 150
5 動的 3 30
6 あーる 45 20
7 機械学習 79 40
8 深層学習 81 20
9 見識 3 90

無事に読み込めていることが確認されました。

3.7.3. 正規表現(regular expression)#

正規表現は文字列などを扱う上でとても便利です。ここでは、その基本的な使い方を紹介します。

ここでは正規表現の初歩とPythonでの扱い方を紹介します。

Pythonでの正規表現

普通の文字は全て正規表現です。

- aという正規表現はaにマッチします。

正規表現の初歩(の初歩)

  • .(ドット)は任意の一文字にマッチ

  • []で囲むと[]の中の文字のいずれかにマッチ

    • [AB]CABもしくはACのいずれかにマッチ

    • [123]010,20,30のいずれかにマッチ

    • 全ての大文字アルファベットのいずれかにマッチは[A-Z]

    • 全ての整数値のいずれかにマッチは[0-9]

  • *は直前の文字の0回以上の繰り返し(0回 == なくても良い)

    • AKB[0-9]*AKB49にもAKBにもAKB1にもマッチ

  • (|)はor(または)という意味になる

    • (A|B)は A or B(AまたはB)にマッチ

  • ソフトなどによって微妙に記法など異なる場合もある。

正規表現は奥が深い…

import re # Pythonで正規表現を扱うモジュール

# まずは、正規表現パターンを「正規表現オブジェクト」にコンパイルし、
# match()やsearch()などのメソッドを使ってマッチングに使えるようにします。

pat1 = re.compile('.')
pat2 = re.compile('[AB]')
pat3 = re.compile('[123]0')
pat4 = re.compile('AKB[0-9]*')
pat5 = re.compile('(A|B)')
# match()は文字列の先頭で0個以上の文字が正規表現パターンにマッチすれば対応するマッチオブジェクトを返します。
# 文字列がパターンにマッチしなければ None を返します

print(pat1.match('a') != None) # マッチすればTrueを返す
print(pat2.match('A') != None)
print(pat3.match('20') != None)
print(pat4.match('AKB49') != None)
print(pat5.match('A') != None)
True
True
True
True
True
# search()は文字列を走査し、正規表現パターンがマッチを生じさせる最初の場所を探して、対応するマッチオブジェクトを返します

print(pat1.search('a'))
print(pat2.search('A'))
print(pat3.search('20'))
print(pat4.search('AKB49'))
print(pat5.search('A'))
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='A'>
<re.Match object; span=(0, 2), match='20'>
<re.Match object; span=(0, 5), match='AKB49'>
<re.Match object; span=(0, 1), match='A'>
# 文字列の置き換えはre.sub

その他文字列の処理でよく使うもの

  • split()    特定の文字列で文字列を分割

  • startswith() 特定の文字列で文字列が始まるかを確認

  • endswith()  特定の文字列で文字列が終わるかを確認

  • strip()    文字列の先頭や末尾の特定の文字列や空白文字(スペース、タブ、改行)を削除

  • replace() 文字列の置き換え

text1 = 'I am happy to hear it.'
text1.split( ) # spaceで文字列を区切る
['I', 'am', 'happy', 'to', 'hear', 'it.']
text2 = '@hogehoge Count me in!'
text2.startswith('@') # ’@’で始まるか確認
True
file1 = 'test_code.ipynb'
file1.endswith('.ipynb') # '.ipynb'で終わるか確認
True
text3 = '     We are able to attend a class.    '

text3.strip() # 文字列と最初の最後の空白文字を削除
'We are able to attend a class.'
text4 = "< aaaaabbbbbbcccccc >"
text4.strip("(<|>)") # 文字列と最初の最後の<または>を削除
' aaaaabbbbbbcccccc '
text5 = 'Hi Max.'
text5.replace('Max', 'Matrix') # Max を Matrixに置き換える
'Hi Matrix.'