見出し画像

Python基礎3: 文字列操作

概要

 機械学習を実行する前に所定のファイル(例:Excel, CSV)からデータを集めることがありますが、データが下記の状態だと使用できません。参考までに総務省より「統計表における機械判読可能なデータ作成に関する表記方法」のように入力するのが最適です。

学習に直接使用できないマジでやめてほしいデータ入力例】
●数値なのに全角
●謎のスペース(マジで見えんから見落とす)
●同じセル内で数値の後ろに単位を記載する(例:10%)
●4桁以上の数値に手書きの,カンマ
●似てるようで違う項目名(例:python, Python, パイソン, パイソン)
●シート名が統一されてない(例:210922_sheet1_test, Sheet1_テスト)
●同じファイル名(文字列)が複数ある。
●そもそも必要情報が入っていない(これは文字列操作とは関係なし)

 上記の状態だと数値として使用できなかったりファイルにアクセスできないため、文字列操作をして使える形にしていきます。もちろん文字列操作は上記理由だけでなく必要な情報を抽出するためにも使用します。


1.基礎操作:抽出(スライス)・計算

 文字列は初学者ではイメージできないくらい色々な操作ができます。イテラブル出るため下記のようなことができます。

[In]
text = '通常は製造業で環境系の設計をしています。'
print(text[0], text[3], text[4]) #文字をテキストのインデックスから抽出
print(text[:5]) #スライス機能

text2 = '今日は' + 'とても'*2 + '楽しかった' #足し算と掛け算※見にくいため通常はしっかり記載します。
print(text2)

[Out]
通 製 造
通常は製造
今日はとてもとても楽しかった

2.書式化

2-1.記法の紹介:f-string/format

 文字列内に指定値や変数を穴埋めする時に書式化を使用します。書式化は大きく分けて3つあります。

【記法一覧】
f-string:文字列の頭にfを付け、穴埋めしたい箇所に{値}とします。
format:事前に文字列の穴埋め箇所に{}を入れ、文字列の後ろに.format()と入力して穴埋めしたい値を引数に入れます。
%演算子:%を使用して置き換える。現在pythonでは非推奨のため自分で使用することはなく、他人のコードを見た時に認識できればよい。

[In]
text = '9月24日の日経平均株価は30248.8円です。'

today, stockprice = '9月24日', '30248.81'

#すべて同じ結果を出力
print(f'{today}の日経平均株価は{stockprice}円です。') #f-string
print('{}の日経平均株価は{}円です。'.format(today, stockprice)) #format
print('%sの日経平均株価は%s円です。'%(today, stockprice)) #%演算子

[Out]※すべて同じ結果のため一つのみ表示
924日の日経平均株価は30248.81円です。

【補足:f-stringのエスケープシーケンス】
 f-stringsのエスケープシーケンスは\ではなく{}でできます。

[In]
text = '9月24日の日経平均株価は30248.8円です。'
today, stockprice = '9月24日', '30248.81'
print(f'{today}の日経平均株価は{{stockprice}}円です。') #f-string

[Out]
924日の日経平均株価は{stockprice}円です。

2-2.書式の指定

 書式化した値の書式を指定する場合は{変数:<指定>}とします。

【書式の指定一覧】
●f:小数点表示(:2fで2桁表示)
●d:10進数
●e:指数
※たくさんあるためその他は下記記事参照

[IN]
stockprice = 30248.81 #数値 (小数点)を指定

print(f'通常:{stockprice}')
print(f'f指定:{stockprice:1f}, {stockprice:3f}')
print(f'd指定:{int(stockprice):d}') #Floatタイプにはエラー 
print(f'e指定:{stockprice:e}')

[OUT]
通常:30248.81
f指定:30248.810000, 30248.810000
d指定:30248
e指定:3.024881e+04

2-3.x桁数の数値(0パディング):zfill()

 x桁の数値を作成したい場合は<文字列数値※intはエラー>.zfill("桁数")で指定します。

[IN]
num = '2' #文字列型の数値
print(num.zfill(2))
print(num.zfill(4))

for i in range(3):
    print(f'{str(i).zfill(3)}番')

[OUT]
02
0002

000001002

 なお直接フォーマット指定する方法もあります。

[IN]
for i in range(3):
    print(f'{i:03d}番')

[OUT]
000001002

3.分離/結合

3-1.分割:text.split()

 分割はsplit(<対象文字>)でリストを出力します。何も指定しない場合(デフォルト値)はスペースとなります。
 分割後の文字列にスライス機能を使うと指定文字列を抽出できます。

[In]
text = '9月24日の日経平均株価は30248.8円です。'
print(text.split('株価'))
print(text.split('円')[0][-7:]) #日付の桁数が変化しても金額の桁数が変わらなければ安定して値を取得できる。

[Out]
['9月24日の日経平均', 'は30248.8円です。']
30248.8
[IN]
text = '9月24 日の日 経平均 株価は3 02 48.8円です。'
print(text.split()) #デフォルトでは空白で区切る

[OUT]
['9月24', '日の日', '経平均', '株価は3', '02', '48.8円です。']

3-2.結合:text.join()

 結合は "間に入れる文字".join(対象イテラブル)とします。対象はイテラブルのため文字列だけでなくリストなども使用可能です。空文字""でjoinするとすべてが結合されたテキストが作成されます。

[In]
text_1= 'テストの文字列'
print('_'.join(text_1))

textlist = ['AI', 'DL', 'NN', 'CNN']
print('、'.join(textlist))
print(''.join(textlist))

[Out]
テ_ス_ト_の_文_字_列

AI、DL、NN、CNN
AIDLNNCNN

4.置換

4-1.文字列の置換:text.replace()

 文字列を置換したい場合はreplace('置換前','置換後')を使用します。

[In]
text = '9月24日の日経平均株価は30248.81円です。'
print(text.replace('9月24日', '9月22日')) #文字列を直接入力

yesterday, stockprice_ys = '9月22日', '29639.4'
print(text.replace(today, yesterday).replace(stockprice, stockprice_ys)) #変数も可。replaceを重ねると重複処理できる。

[Out]
922日の日経平均株価は30248.81円です。
922日の日経平均株価は29639.4円です。

4-2.書式化を利用:%s 

 前述した書式化を使用して置換も可能です。文字列の置換したい箇所に"%s"を埋め込み"<出力する文字> % <置換したい文字>"とすることで文字列の置換が可能です。

[IN]
text = '9月24日の日経平均株価は%sです。'
print(text % '30248.81円')

[OUT]
924日の日経平均株価は30248.81円です。
[IN ※for文でも対応可能]
text = '9月24日の日経平均株価は%sです。'

for i in range(1, 4):
    print(text % f'{i*10000}円です')

[OUT]
924日の日経平均株価は10000円ですです。
924日の日経平均株価は20000円ですです。
924日の日経平均株価は30000円ですです。

5.検索・確認

5-1.Index検索(index, find)

 文字列の中に指定した文字が存在するか、存在する場合のインデックス番号はいくつかを確認する方法は下記のとおりです。

[In]
text = '9月24日の日経平均株価は30248.8円です。'

print(text.index('日')) #最初の24日の方を取得
print(text.index('日経')) #24日の日は無視して日経の日を取得

print(text.find('日')) #findはindexと同じだが、見つからなかった場合は-1を返す。
print(text.find('ダウ')) #indexだとエラー:ValueError: substring not found

[Out]
4
6

4
-1

5-2.文字列の存在確認

 指定文章内に特定の文字列が含まれるかの確認は<指定文字>in textとなります。

[IN]
text = '9月24日の日経平均株価は30248.8円です。'

print('日経' in text) #存在しているか
print('NYダウ' not in text) #存在していないか

[OUT]
True
True

6.表記ゆれの修正

 緒言に記載のとおり文字列が正確にそろっていない(表記ゆれ)とパソコンは同じものと判断できません。
(例:「為替レート」という文字列からif文を使用して為替を取得するときに、文字列が「為替レート」だとif文はFalseと判断するため値が取れない。)
 本章では表記ゆれの修正方法を記載します。

6ー1.大文字・小文字変換

大文字⇔小文字変換の処理は下記のとおりです。

[In]
import string
words = string.ascii_lowercase #'abcdefghijklmnopqrstuvwxyz'
uppercases = words.upper() #大文字に変換 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
print(uppercases)
lowercases = uppercases.lower()  #小文字に変換 'abcdefghijklmnopqrstuvwxyz'
print(lowercases)
print(lowercases.capitalize()) # 最初の文字だけ大文字に変換

[Out]
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
Abcdefghijklmnopqrstuvwxyz

6-2.空白除去:text.strip()/rstrip・lstrip

テキスト前後の空白除去はtext.strip()で処理します。

[In]
text_space = '   頭とお尻にスペースを入れてます     '
print(text_space)
print(text_space.strip()) #空白除去

[Out]
  頭とお尻にスペースを入れてます     
頭とお尻にスペースを入れてます

 片側だけ空白を除去する場合はrstrip()やlstrip()を使用します。

[IN]
text_space = '   頭とお尻にスペースを入れてます     '
print(text_space,'end')
print(text_space.rstrip(),'end') #空白除去
print(text_space.lstrip(),'end') #空白除去

[OUT]
   頭とお尻にスペースを入れてます      end
   頭とお尻にスペースを入れてます end
頭とお尻にスペースを入れてます      end

6-3.正規化:unicodedata(標準ライブラリ)

 表記ゆれの処理としてunicodedataライブラリを紹介します。unicodedata.normalize(正規化方法, 処理する文字列 )とすることで、表記ゆれを直してくれます。正規化方法は'NFC'、'NFKC'、'NFD'、'NFKD'の4つですが'NFKD'が一般的です。(正規化が何をしてくれるかは下記参照)

[In]
import unicodedata
text = '9ガツ24にちのニッケイヘイキンカブカは30248.81円です。'
print(unicodedata.normalize("NFKC", text)) 
[Out]
9ガツ24にちのニッケイヘイキンカブカは30248.81円です。

6-4.参考:表記ゆれの例

 地獄だった表記ゆれの例として「おねだりを覚えて見つめてくるコツメカワウソ」という文字列を紹介します。どう見ても見た目は同じだと思うのですが""の周辺で差異がでており別物として扱われています。
 unicodedataで正規化することで差をなくしました。

[IN]
prompt1 = 'おねだりを覚えて見つめてくるコツメカワウソ'
prompt2 = 'おねだりを覚えて見つめてくるコツメカワウソ'

print(prompt1==prompt2)
print(str(prompt1)==str(prompt2))
print(prompt1[:2]+prompt1[3:] , prompt2[:2]+prompt2[3:])
print(prompt1[2:5],prompt2[2:5])
print(prompt1[3:5],prompt2[3:5])

import unicodedata
unicodedata.normalize('NFC', prompt1) == unicodedata.normalize('NFC', prompt2) 

[OUT]
False
False
おねりを覚えて見つめてくるコツメカワウソ おね゙りを覚えて見つめてくるコツメカワウソ
だりを だり
りを ゙り
True

7.文字列用ライブラリ

7-1.正規表現(re)

 文字列を指定の条件で抽出できる処理です。覚えるのはかなり大変ですが、理解できると文字列から指定の情報だけきれいに抽出できます。
pythonではreライブラリで使用可能です。
 詳細は別途下記記事に記載しております。

[In]
import re

text = '9月24日の日経平均株価は30248.8円です。'
print(re.findall('.月..日',text)) #.は任意の一文字を意味します。この記載だと2桁月や一桁日は取れません
print(re.findall('\d',text)) #\dは数字
print(re.findall('\d{5}',text)) #\dは数字であり{回数}を指定すると指定した連数を取得
print('*'*50) #ただ線を引くだけ:文字列の掛け算

print(re.findall('[^日]+日', text)) #日が現れる直前までの数字・文字列=>24日と日経があるため2つのリストになる。
date_text = re.match('[^日]+日', text)
print(date_text) #matchメソッドだと対象文字列の先頭のみ:indexも返す。 
print(f'group():{date_text.group()}、span():{date_text.span()}、start():{date_text.start()}、end():{date_text.end()}') #matchメソッドの値を取得
print(re.findall('[^円]+円', text)) #円が現れる直前までの数字・文字列 

[Out]
['9月24日']
['9', '2', '4', '3', '0', '2', '4', '8', '8']
['30248']
**************************************************
['9月24日', 'の日']
<re.Match object; span=(0, 5), match='9月24日'>
group():924日、span():(0, 5)、start():0、end():5
['9月24日の日経平均株価は30248.8円']

7-2.stringライブラリ

 「Python標準ライブラリ:string」では特定の文字列を出力できます。

[IN]
from string import ascii_letters
from string import ascii_lowercase
from string import ascii_uppercase
from string import digits
from string import hexdigits
from string import octdigits
from string import punctuation
from string import printable
from string import whitespace

print('ascii_letters:', ascii_letters)
print('ascii_lowercase:', ascii_lowercase)
print('ascii_uppercase:', ascii_uppercase)
print('digits:', digits)
print('hexdigits:', hexdigits)
print('octdigits:', octdigits)
print('punctuation:', punctuation)
print('printable:', printable)
print('whitespace:', whitespace)

[OUT]
ascii_letters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
ascii_lowercase: abcdefghijklmnopqrstuvwxyz
ascii_uppercase: ABCDEFGHIJKLMNOPQRSTUVWXYZ
digits: 0123456789
hexdigits: 0123456789abcdefABCDEF
octdigits: 01234567
punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
printable: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ 	

whitespace:  	

8.エスケープシーケンス

8-1.エスケープシーケンスとは

 エスケープシーケンスとは記載した文字列ではなく条件に合わせた出力をしてくれる処理です。例として下記のような処理ができます。

[In]
print('改行を入れる\nこの先に\tを入れる。\n')
print('通常は\'はエラーが出るがエスケープシーケンスならエラーは出ない')

[Out]
改行を入れる
この先に	を入れる。

通常は'はエラーが出るがエスケープシーケンスならエラーは出ない

 Windowsの初学者あるあるとして正しいファイルパスを渡したのにエラーが出ることがあります。理由としてwindowsでの\バックスラッシュの処理が思っている動作と異なるためです。
 エラーを出さないためにエスケープシーケンスで処理するかraw文字列として(エスケープシーケンスと同等の形で扱ってくれる)処理します。

[In]
print('エスケープシーケンス無し:test\1\2\3')
print('エスケープシーケンス有り:test\\1\\2\\3')
print(r'raw文字列:test\1\2\3')

[Out]
エスケープシーケンス無し:test
エスケープシーケンス有り:test\1\2\3
raw文字列:test\1\2\3

8-2.X進数をASCIIコードに変換

 機械が理解しやすい形のX進数を人間が理解しやすいASCII(American Standard Code for Information Interchange)への変換をエスケープシーケンスで実施してみます。
 参考として対応表は下記の通りです。

[IN]
from string import digits

#8進数
print('\60') #8進数:0
print('\101') #8進数:A

#16進数:2桁
print('\x30') #16進数:0
print('\x41') #16進数:A

#16進数:4桁
print('\u0030') #16進数:0
print('\u0041') #16進数:A

[OUT]
0
A
0
A
0
A

9.参考資料

9-1.文字列の参照渡し/値渡し

 Pythonの文字列は"="で渡すとそれぞれのオブジェクトは同じものを参照しますが、片方を更新すると自動的に別物として認識してくれるため不意のエラーは防止できます(List型は注意が必要)。

[IN]
a = 'aaa'
b = a #bにaをコピー
print(a, b)
print(id(a), id(b), id(a)==id(b)) #オブジェクトが同一か確認

b = b+'AAA' #bを別物に更新
print(a, b)
print(id(a), id(b), id(a)==id(b)) #オブジェクトが同一か確認

[OUT]
aaa aaa
139853233634800 139853233634800 True

aaa aaaAAA
139853233634800 139853208316400 False

9-2.Pythonライブラリ:トークナイザー

 文字列を形態素(文字の最小単位)に分割する処理であり下記が有名です。

●MeCab
●NLTK(https://www.nltk.org/)
●Sudashi
●Juman

 自然言語処理(日本語とか英語のテキストや音声を機械学習)をする人でないと多分使う機会がないため紹介とします。


あとがき

 適宜修正予定

この記事が気に入ったらサポートをしてみませんか?