Pythonのメモ帳

numpy, pandas, tensorflow を使いこなすための忘備録

マトリクス形式 (配列) から スプレッド形式 (表) に変換するときも numpy.meshgrid が役立つ

マトリクス形式(配列)のデータをスプレッド形式(表)に変換したい場合がある。

 

例えば下のような配列を、、

 [[0.36363082, 0.00332102, 0.14533355],
 [0.11039035, 0.95780551, 0.48281694],
 [0.44771602, 0.94823649, 0.36095112]]

 

↓ こんなふうな表にしたいという意味。

 
row
col
val
0 0 0 0.190805
1 0 1 0.599887
2 0 2 0.209723
3 1 0 0.105745
4 1 1 0.219456
5 1 2 0.198315
6 2 0 0.671719
7 2 1 0.537687
8 2 2 0.012490

 

工夫なしに書くとこんなコードになる。

import numpy as np
import pandas as pd

# テストデータ
mat = np.random.rand(3,3)

# スプレッドシート形式に変更                
cols, rows, vals = [],[],[]
for row in range(mat.shape[0]):
    for col in range(mat.shape[1]):
        rows.append(row)
        cols.append(col)
        vals.append(mat[row,col])
                
df = pd.DataFrame()
df['row'] = rows
df['col'] = cols
df['val'] = vals

 

これにはもっと高速にできる方法があって、npのmeshgridという関数を使う。これを使うとfor構文が不要になり、コードもシンプル。

# スプレッドシート形式に変更 
rows,cols = np.meshgrid(range(mat.shape[0]),range(mat.shape[1]),indexing='ij') df = pd.DataFrame() df['row'] = rows.flatten() df['col'] = cols.flatten() df['val'] = mat.flatten()

 

ちなみにこれ、前回書いた内容とやってる事はほとんど同じで、使い道がちがうだけでした。

spcx8.hatenablog.com

 

2次元配列の値を行番号や列番号に依存させたい場合、meshgrid()を使って高速化

2次元配列を作るときに、中身の値を行番号や列番号に依存させたい場合がある。

つまり↓のような関係が成り立つようにしたい。

2次元配列の値 = 関数( 行番号, 列番号 )

 

普通に書くとこうなる。

import numpy as np

# 配列作成
nrows = 10
ncols = 10
mat = np.empty([nrows,ncols])

for row in range(nrows):
    for col in range(ncols):
        mat[row,col] = row + col

ここでは配列の値 =  行番号 + 列番号 となるようにしている。

このコードで作ったmatを一応可視化すると、

f:id:spcx8:20180909113636p:plain

右下に行くほど値が大きくなっている。

 

そしてもっといい方法がこちら。
 numpy にはmeshgridという便利な関数が用意されていて、forを使わずに書くことができる。

# 配列作成
nrows = 10
ncols = 10

rows,cols = np.meshgrid(range(nrows),range(ncols), indexing=’ij’)
mat = rows + cols 

結果は同じなので割愛。

 

ちなみに、meshgridを使うと処理速度も改善できる。というかmeshgridを使うのはこれが目的だったりする。

nrows, ncols ともに 10,000の場合で計測した結果、

- 前述のコード(for2段階) 31.6 sec

- 後述のコード(meshgrid) 1.8 sec

とその差は明らか。自分でfor構文を書くのは極力やめた方がいいらしい。

pandas / DataFrame の中身の文字列を一括で分割して別の列として定義する

pandasで、特定の列の(中身の)文字列を分割して使いたいときがある。

 

例えばこんな表があったとして、

 
NumName
0 a001_name1
1 a002_name2
2 a003_name3

 ↓  こんな風にしたい

 
NumName
num
name
0 a001_name1 a001 name1
1 a002_name2 a002 name2
2 a003_name3 a003 name3

 

この処理をするのに、以前はこんなもっさりしたコードを書いていた。

分割したい列の中身を取り出して、1行ずつ分割処理するやり方だ。

vals  = df['NumName'].values
nums  = []
names = []

for val in vals: num,name = val.split('_') nums.append(num) names.append(name) df['num'] = nums df['name'] = names

 

でも下のようにたった2行で書けることがわかった。

df['num']  = df['NumName'].str.split(pat='_', expand=True)[0]
df['name'] = df['NumName'].str.split(pat='_', expand=True)[1]

for を使わないので処理速度も上がるかもしれない。(pandasが裏で効率よくやってくれているはず) 

pandas公式ページ参照:
 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.str.split.html

 

 ちなみ1行目にある .str が便利なやつで、これを使うと文字列の処理を列単位で一括でやってくれる。文字列操作 replace() もご覧の通り。

df['NumPlace'] = df['NumName'].str.replace('name','place')
 
NumName
num
name
NumPlace
0 a001_name1 a001 name1 a001_place1
1 a002_name2 a002 name2 a002_place2
2 a003_name3 a003 name3 a003_place3

 pandasのおかげで本当に仕事がはかどる。

 

ダイアログボックスでファイル名を指定したい(Windows/Macでコードが違う)

データを取り込む時にはファイル名を指定する。
pd.read_csv()でも np.loadtxt()でもそう。

でファイル名を指定するのにコードをいちいち書き換えたくないので、
ダイアログボックスを使う方法を調べた。

調べたらtkinterというライブラリを使うみたいだが、これがPython2系とPython3系でコードが色々と違うみたい。Python3系向けにここでメモ。

OSが違うとコードの書き方が変わるので注意。

 

windowsの場合

import tkinter as tk
import tkinter.filedialog as tkdialog
import os

root  = tk.Tk()
fname = tkdialog.askopenfilename(filetypes=[('data files','*.csv;*.txt')],initialdir=os.getcwd())
root.withdraw()

引数filetypesでダイアログボックスに表示される拡張子を制限指定できる。ここではcsv,txtに絞ってるけど、すべて表示したければ,

filetypes=[('all files','*.*')]

とすればOK。

 

Macの場合

macOSwindowsではfiletypesの書き方が違うようで、上のコードではファイルが指定できなかった。こう書けば大丈夫だった。

root  = tk.Tk()
fname = tkdialog.askopenfilename(filetypes=[("csv files","*.csv"),("txt files","*.txt")],initialdir=os.getcwd())
root.withdraw()

ただ、windowsの時のように全てのファイルが選択できるようにする方法はわかりません。

 

 

最後に、どちらの場合も一緒にimportしているosは現在のディレクトリを取得するのに使っているだけなので不要なら引数の部分と一緒に削除してOK。

pandas の append処理で行ラベル(index番号)がおかしくなった時に行ラベルをリセットする

pandas で append処理(2つのDataFrameを縦に連結)した時、行ラベル(index番号)は順番通りにならなくなる。

 例えばこう。

import pandas as pd
import numpy as np

data = range(10)
data = np.array(data).reshape([5,2])
df1 = pd.DataFrame(data,columns=['col0','col1'])
df2 = pd.DataFrame(data,columns=['col0','col1'])
df  = df1.append(df2)
print(df)

Out:

  col0 col1
0 0 1
1 2 3
2 4 5
3 6 7
4 8 9
0 0 1
1 2 3
2 4 5
3 6 7
4 8 9

 

 

df.loc()など行ラベルを参照する処理でこれがエラーを招くことがある。

そんな時は行ラベルを割り振り直すことで対処できる。

reset_index()を使う。

df = df.reset_index(drop=True)

ちゃんと返り値を保持することを忘れずに。

2つの文字列配列の要素同士を連結する(文字列配列の足し算)

numpyでは配列(行列)の計算が簡単にできる。これは要素が数字の場合だけでなく、ありがたいことに文字列の場合にも使える。文字列の場合にできる計算は足し算(文字列の連結)だけで、使うこともあまりないけど地味にありがたい。

import numpy as np

array1 = np.array([['a', 'b', 'c'],['a', 'b', 'c']], dtype=object)
array2 = np.array([['1', '2', '3'],['1', '2', '3']], dtype=object)
print(array1 + array2)
出力: array([['a1', 'b2', 'c3'],
['a1', 'b2', 'c3']], dtype=object)

 

 

だだし、配列の型の設定をしないとエラーになるので注意。下のように引数の dtype=object を除くとエラーになる。

array1 = np.array([['a', 'b', 'c'],['a', 'b', 'c']])
array2 = np.array([['1', '2', '3'],['1', '2', '3']])
print(array1 + array2)

出力: TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U1') dtype('<U1') dtype('<U1')

 

 

もちろん後から型を変換しても大丈夫。

array1 = np.array([['a', 'b', 'c'],['a', 'b', 'c']])
array2 = np.array([['1', '2', '3'],['1', '2', '3']])
print(array1.astype(object) + array2.astype(object))
出力: array([['a1', 'b2', 'c3'],
['a1', 'b2', 'c3']], dtype=object)

  

 

 

 

 

DataFrameからリストやnumpy.ndarrayへの変換

DataFrameからnumpy.ndarrayへの変換

.valuesと付け足すだけ。pandas.Seriesの場合も同様の処理で変換が可能。

import numpy  as np
import pandas as pd

dataframe = pd.DataFrame([[1, 2, 3], [4, 5, 6]], dtype=int)
array1    = dataframe.values.tolist()
print(array1, type(array1))

series = pd.Series([11, 12, 13], dtype=int)
array2 = series.values.tolist()
print(array2, type(array2))

 

DataFrameからリストへの変換

.valuesで一度numpy.ndarrayにしたのち、numpy.tolist()を使う。

list1 = dataframe.values.tolist()
print(list1, type(list1))

list2 = series.values.tolist()
print(list2, type(list2))

 

ndarrayからDataFrameへの変換

念のため逆も書いておく。

df_new     = pd.DataFrame(array1)
series_new = pd.Series(array2)

 

リストからDataFrameへの変換
df_new     = pd.DataFrame(list1)
series_new = pd.Series(list2)