TL;DR
- もしテーブルデータを扱うなら
- Python 簡単に使いはじめられるよ
- Pandas すごく使いやすいよ
- なおかつbashに比べてめちゃくちゃ速度出るよ
はじめに
インフラエンジニアだったんですよ、ぼく。
なので、bashで書くshell scriptはそれなりに書いていました。
それなりですけど。
開発を初めて2年目の今、
100万行ほどのデータの整形を求められました。
最初の1年はJavaを使って開発、ほぼIDE使っていて
いわゆるツール的な使い方に慣れていなかったので、
Javaでの処理はイメージがわきませんでした。
(開発環境のセットアップもしてないし)
というところで、簡単に作れるツールといえば、ShellScriptでしょう
と思い手を出しはじめました。
やりたいこと
行数:100万
列数:400
からなるcsvデータの1カラム「location」を分割して
latitude/longitudeにしたい。
latitude/longitudeは共に数値(少数)で
"(latitude,longitude)" という形で入っている。曲者。
↓これを
id | location | あと色々400列くらい |
---|---|---|
1 | (35.00,135.00) | |
2 | (35.18,135.12) |
↓こうしたい
id | latitude | longitude | あと色々400列くらい |
---|---|---|---|
1 | 35.00 | 135.00 | |
2 | 35.18 | 135.12 |
色々試したこと
エクセル先生
ロジカルじゃない僕でも直感で操作ができるエクセル先生
SIerをやっているときには散々お世話になりました。
時間はかかるだろうが、コード書かなくても簡単にできるなら
まずは先生の出番じゃないか?と思いました。
手順
- location列の右に列追加
- location列のみを対象に、データ -> 区切り文字の変更でカンマ区切りにする
- locationの見出しを変更
- 保存
できた?
できませんでした。
エクセルはSJISなんですよ。忘れてました。
csvファイルはutf-8
なので、日本語のカラムが文字化けしちゃうんですね。
(保存時にデータが欠損しました。)
bashでiconv使って文字コード変えてからやろうとも思いましたが、
エラー出ちゃったので細かく見ずにやめました。
google スプレッドシート
csv大きすぎてインポートできない
Numbers
そもそも行列の数が足りない
最初に作ったscript
こんな感じで作りました。
とりあえずの品なので、細かいところはご愛嬌
#!/bin/bash
INPUTFILE=$1
OUTFILE=$2
ROWS=$(cat ${INPUTFILE}) # catをheadに変えてデバッグしていた
cnt=0
while read LINE
do
cnt=`expr ${cnt} + 1`
# ヘッダ行用の処理
if [ ${cnt} -eq 1 ]
then
fld1=$(echo -n ${LINE} | cut -d, -f2-14)
fld2="latitude"
fld3="longitude"
fld4=$(echo ${LINE} | cut -d, -f16-217) # 218列目になんかゴミデータがあった
fld5=$(echo ${LINE} | cut -d, -f219-)
echo ${fld1},${fld2},${fld3},${fld4},${fld5} > ${OUTFILE}
else
fld1=$(echo -n ${LINE} | cut -d, -f2-14)
location=$(echo -n ${LINE} | cut -d, -f15)
if [ -n "${location}" ]
then
location=$(echo -n ${LINE} | cut -d, -f15-16)
fld2=$(echo -n ${location} | cut -d, -f1| sed -e 's/"(//g')
fld3=$(echo -n ${location} | cut -d, -f2| sed -e 's/)"//g')
fld4=$(echo ${LINE} | cut -d, -f17-218)
fld5=$(echo ${LINE} | cut -d, -f220-)
echo ${fld1},${fld2},${fld3},${fld4},${fld5} >> ${OUTFILE}
fi
fi
done <<_EOD
${ROWS}
_EOD
何も考えず、コマンドを多用しているわけですが、
処理時間が一行あたり50msでした。
100万行処理するとおよそ13時間
やりたいことはできるんだけど、、、
pythonとpandas
準備
python のインストールと
$ pip3 install pandas
これぐらいですね。
作ったもの
import pandas as pd
import time
def getLatitude(st):
st2=st.replace("(","").replace(")","").split(",")
return float(st2[0])
def getLongitude(st):
st2=st.replace("(","").replace(")","").split(",")
return float(st2[1])
def run(df):
res = df.assign( # location列からlatitude列を作成する関数実行
latitude=csv_input.apply(lambda x: getLatitude(x['location']), axis=1)
).assign( # location列からlongitude列を作成する関数実行
longitude=csv_input.apply(lambda x: getLongitude(x['location']), axis=1)
).drop( # 列削除
columns=['location']
)
return res
if __name__ == "__main__":
start = time.time()
# ファイルをデータフレームとして読み込み
df = pd.read_csv(filepath_or_buffer="./input.csv", encoding="utf8", sep=",")
# データ加工
res = run(df)
# csvでアウトプット
res.to_csv("output.csv")
# かかった時間の計測&出力
elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
いけてないだろう箇所はたくさんあると思いますが、
それでもなんと、5分くらいで処理が終わる
当者比160倍
なんて速いんだ
ググったところ
pandasよりnumpyを使った方が高速らしいが
ひとまずこれで、ShellScriptよりも安全な(bashのあのコードだと、カラムの中に区切り文字があると狂ってしまう)処理がかけたし
13時間が5分になったので、満足
おわりに
これを機にデータ処理関連を学んでいこうと思いました。