LoginSignup
4
1

More than 5 years have passed since last update.

bashしか知らない俺に速度160倍のcsv処理能力を与えたpandaす

Posted at

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をやっているときには散々お世話になりました。
時間はかかるだろうが、コード書かなくても簡単にできるなら
まずは先生の出番じゃないか?と思いました。

手順

  1. location列の右に列追加
  2. location列のみを対象に、データ -> 区切り文字の変更でカンマ区切りにする
  3. locationの見出しを変更
  4. 保存

できた?

できませんでした。
エクセルは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分になったので、満足

おわりに

これを機にデータ処理関連を学んでいこうと思いました。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1