How to make datas our friends

「エンジニアは発信していくことが責務である」という言葉に感化されて始めた勉強したことを書き留めていく備忘録的なやつ。

Pythonで文章を単語に分解し各単語の文字数を先頭から出現順に並べたリストを作成する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

Pythonで"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文章を単語に分解して各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成する。

結果

文章を単語ごとに分割して分割した単語の文字数をfor文でリスト内にinsertして対応しました。

import re
sentense = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
splited = re.split('\s', sentense.replace('.', ''))
cntlist = []
for i in range (0, len(splited)):
    cntlist.insert(i, len(splited[i]))
splited
> [3, 1, 4, 1, 6, 9, 2, 7, 5, 3, 5, 8, 9, 7, 9]

解説/考察

Pythonの標準ライブラリのreを使用しました。
6.2. re — 正規表現操作 — Python 3.6.1 ドキュメント

reを使用して単語を分割するとコンマが残ってしまったのでreplaceを使用してピリオドを文章内から抹消しています。他にも方法あるんですかね?

> sentense = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
> sentense.replace('.', '')
> 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics'

reのsplit関数を使用して文章を分割しました。
今回は任意の空白文字とマッチする部分で分けたいのでpatternに\sを指定してます。
\W+でも問題ないっぽいです。

正規表現はここにまとまっています。
正規表現 HOWTO — Python 3.6.1 ドキュメント

> import re
> sentense = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
> re.split('\s', sentense.replace('.', ''))
> ['Now', 'I', 'need', 'a', 'drink,', 'alcoholic', 'of', 'course,', 'after', 'the', 'heavy', 'lectures', 'involving', 'quantum', 'mechanics']

文章を単語ごとに分解できたので、分割した単語数分for文でリストにデータを流し込む作業をしています。

# 空のリストを用意
cntlist = []
# len(splited)で分割した単語数を取得して値を渡してあげる
for i in range (0, len(splited)):
    #リストのi番目に分割したi番目の単語の文字数をinsertする
    cntlist.insert(i, len(splited[i]))

以上!

Pythonで2つの文字列の文字を先頭から交互に連結する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

Pythonで文字列"パトカー"と"タクシー"の文字を先頭から交互に連結して文字列"パタトクカシーー"にする。

結果

以下の方法で2つの文字列を先頭から交互に連結しました。

> str1 = "パトカー"
> str2 = "タクシー"
> str3 = ""
> for i in range(0,4):
>      str = (str1 + str2)[i::4]
>      str3 += str
> str3
> 'パタトクカシーー'

解説/考察

一番最初に書いたやつがこれ。

> str1 = "パトカー"
> str2 = "タクシー"
> str3 = (str1 + str2)[0::4]
> str4 = (str1 + str2)[1::4]
> str5 = (str1 + str2)[2::4]
> str6 = (str1 + str2)[3::4]
> str3 + str4 + str5 + str6
> 'パタトクカシーー'

s[i:j:k] を使用して"パトカー"と"タクシー"を合体させた文字列のi番目から4文字おきに文字を取ってきて最終的に合体。
でも、なんかいけていないってことでfor文で単純作業の部分を書き直しました。(やってることは同じですが)

他にも方法がありそう。
どう書くのがイケてる方法なのだろう。。。

pythonで「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列にする

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

Pythonで文字列"パタトクカシーー"の1,3,5,7文字目を取り出して連結した文字列にする。
(つまり"パトカー"になれば成功)

結果

以下の方法で1,3,5,7文字目を切り出して文字を連結しました。

> str = 'パタトクカシーー'
> str[::2]
> 'パトカー'

解説/考察

記事にするまでもないほど簡単でした。
s[i:j:k] の k に2を指定して文字列を2文字ごとに切り出せばOK。

詳細は前記事でまとめています。
024minion.hatenablog.jp

ちなみに str[1::2] にしてあげれば"タクシー"になりました。

> str = 'パタトクカシーー'
> str[1::2]
> 'タクシー'

Pythonで文字列を逆順にする

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

Pythonで文字列"stressed"を逆順で表示する。
(つまり"desserts"になれば成功)

結果

以下の方法で文字列を逆順にできました。

> str = 'stressed'
> str[::-1]
> 'desserts'

解説/考察

2.3.6 Sequence Types -- str, unicode, list, tuple, buffer, xrangeを参考にしました。
sequence type の s は s[i:j:k] を使用して文字列をスライスできるらしい。

s[i] = i'th item of s, origin 0
s[i:j] = slice of s from i to j
s[i:j:k] = slice of s from i to j with step k

日本語に翻訳するとこんな感じでしょうか。
(日本語のリファレンスもどこかにあるのでしょうか?w)

s[i] = 文字列sから0を起点にしてi番目文字を切り出す
s[i:j] = i番目からj番目の文字列を文字列sから切り出す
s[i:j:k] = i番目からj番目の文字列を文字列sからk個おきに切り出す

まず s[i] の動作について一通り試してみました。

> str = 'stressed'
> # 0が起点なので0を指定すると1番目の文字"s"が切り出される
> str[0]
> 's'
> # 同様に0が起点なので1を指定すると2番目の文字"t"が切り出される
> str[1]
> 't'
> # マイナス値を指定すると文字列のけつからi番目の文字を切り出せるっぽい
> str[-1]
> 'd'
> # 文字列の文字数より大きい値は指定できない
> str[9]
> IndexError: string index out of range

続いて s[i:j] も一通りいじってみる。

> # 何も指定しないと全て切り出される
> str[:]
> 'stressed'
> # 0起点に6文字分切り出す
> str[0:6]
> 'stress'
> # 0起点なので2文字目から6文字分切り出す
> str[1:6]
> 'tress'
> # s[i]と違い文字数より大きい値を指定しても怒られない(ただし何も出ない)
> str[10:10]
> ''
> # jを指定しないでiにマイナス値を指定するとけつからi番目までの文字が切り出される
> str[-4:]
> 'ssed'
> # iが文字列の文字数より大きくてもけつで止まる
> str[-9:]
> 'stressed'

最後に s[i:j:k] です。

> # 何も指定しないと全て切り出される
> str[::]
> 'stressed'
> # 1文字づつ切り出す
> str[::1]
> 'stressed'
> # 2文字づつ切り出す
> str[::2]
> 'srse'
> # -1文字づつ切り出す(けつまで繰り返す)
> str[::-1]
> 'desserts'
> # -2文字づつ切り出す(けつまで繰り返す)
> str[::-2]
> 'dset'

以上のことから str[i:j:k] の k に-1を指定すれば文字列を逆順で切り出せる。

CSVファイルをローカルのMySqlDBに取り込む

0. 概要

AppsFlyer(計測ツール)から落としてきたCSVのローデータを、ローカルのMySqlDBに取り込むのに若干苦戦したので、無事成功するまでの過程を後世に残そうと思いメモ。かなり基礎的な技術だと思われるので、対象読者は初心者向けだと思われる。

今回ハマったポイント

CSVの1行目(ヘッダー部分)まで読み込んでしまい型が違うと怒られる。

・ローデータのCSV内にある文字列"null"をNULL値として扱ってもらえず、
 INTEGER型のカラムで型が違うと怒られる。

・LOAD DATA INFILEコマンドを実行しても
 1行目のデータしかテーブルにINSERTされない。

 

はい、見ての通りめちゃ初歩的なミスだと思います(ΦωΦ)

(だって初めてやったんだもん、大目に見て欲しい)

1. 前準備

ローカルDBにCSVを取り込むためにデータベースとテーブルを用意する。
今回はこんな感じで用意しました。

データベースを用意(`・ω・´)ゞ

-- 新しいデータベースを作成する
mysql> CREATE DATABASE localdb_jp;

 テーブルを用意(`・ω・´)ゞ

-- 新しいテーブルを作成する
mysql >
CREATE TABLE tmp_localdb_jp.appsflyer_data (
~~~ 省略 ~~~
);

2. CSVを用意したテーブルに読み込む

早速DLしてきたCSVファイルをMySQL側でロードさせてみる。


MySQLにはLOAD DATA INFILEコマンドという、CSVをさくっと読み込んでくれる超絶便利なコマンドがあるのでそれを使うことにする。
(INSERT文を作って読み込ませるよりめちゃ早い)

参照:MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.6 LOAD DATA INFILE 構文

▼構文はこんな感じ

LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name'
    [REPLACE | IGNORE]
    INTO TABLE tbl_name
    [PARTITION (partition_name,...)]
    [CHARACTER SET charset_name]
    [{FIELDS | COLUMNS}
        [TERMINATED BY 'string']
        [[OPTIONALLY] ENCLOSED BY 'char']
        [ESCAPED BY 'char']
    ]
    [LINES
        [STARTING BY 'string']
        [TERMINATED BY 'string']
    ]
    [IGNORE number {LINES | ROWS}]
    [(col_name_or_user_var,...)]
    [SET col_name = expr,...]

SET文を使えば色々融通が効きそうな予感、でも今回は使わない。

早速それっぽいものを作成して実行してみる。

▼クエリ

mysql >
LOAD DATA INFILE "/PATH/TO/data.csv"
INTO TABLE tmp_localdb_jp.appsflyer_data
FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY "\n";

▼実行結果

ERROR 1292 (22007): Incorrect datetime value: 'attributed touch time' for column 'attributed_touch_time' at row 1 

早速怒られる。

そりゃそうだ、そんな簡単に成功しないのは想定内だぜ。

どうやら、1行目もデータとして読み込んでしまっているのが原因で、カラムの型と実際のデータ型が違うよとのこと。

よく見たら構文のところに一行目を飛ばして読み込むっぽいオプションがある。(フィーリングで書き始めないでちゃんと見ましょうねというオチ)

IGNORE number { LINES | ROWS}

早速、それっぽくクエリに足して再度実行してみる。

▼クエリ

mysql >
LOAD DATA INFILE "/PATH/TO/data.csv"
INTO TABLE tmp_localdb_jp.appsflyer_data
FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY "\n"
IGNORE 1 LINES;

▼実行結果

ERROR 1366 (HY000): Incorrect integer value: 'null' for column 'event_revenue' at row 1

はい、また怒られたー(´^ω^)

今度はnullをNULL値として扱ってもらえずINTEGER型のカラムに文字列が入ってきてるぜ親分!的なエラーです。(その辺柔軟にやってくれよ)

あまり作業量は増やしたくないのですが、渋々CSV内のデータを置換するハメに。(もっといい方法あるなら教えて欲しいんだ)

とりあえず、シェルさんになんとかしてもらう。

# LC_ALL=C sed -i -e 's/null/NULL/g' data.csv

これでひとまずnullがNULLになったのでもう一回トライしてみる。

無事通りました(`・ω・´)ゞキリ

mysql> SELECT COUNT(*) FROM tmp_localdb_jp.appsflyer_data;

+----------+

| COUNT(*) |

+----------+

|     3006 |

+----------+

1 row in set (0.06 sec)

番外編:LOADしてもデータが1行分しか読み込まれない 

 実はここに至るまでに、実行しても1行文しかデータが読み込まれないという状況に悩まされていました。

 

原因は、文字コードの違いで(余計なことして改行コードが変わっていたっぽい)LINES TERMINATED BY "\n"の部分を\nから\rにすることで解決しました。

 

どちらを使えばいいかわからない人はCSVファイルをtxtエディタで一回開いて、コマンドF(検索)で両方検索してみるとどちらの改行コードが使われているかすぐにわかります。