Published Date : 2019年8月16日2:10

Python学習者のためのNim Part 2
Learning Nim for Python Users Part 2


This blog has an English translation


PandasライクなNimDataとその他
NimData like Pandas, and other library.

前回の続きです。

nimble import NimData
でNimDataをインポートしたら、さっそく挙動を確かめてみましょう。 例によってPythonコードを先に書き、同じような動作ができるか確かめてみます。

This is a continuation of the previous blog. Now that you've imported NimData with nimble import NimData, it's time to see how it works. As an example, I'll write Python code first to see if it works the same way.

やりたいことは前回落としてきたCSVファイルを開いて、 ちょっと編集して表示させるだけです。 これが思いの他手こずりました。

What I want to do is to open the CSV file we downloaded last time. I just need to edit it a little and display it. This was more than I expected.


Python Code [example_pd.py]
import pandas as pd
import time

start = time.time()

# 文字コードをCP932(Shift-JIS)でCSVファイルを読み込み、データフレームオブジェクトにする。
# Read the CSV file with CP 932 (Shift-JIS) to make it a data frame object.
df = pd.read_csv('../zenkoku/zenkoku.csv',sep=',',encoding='cp932')

# 行数を確認。
# Check the number of rows.
print(df.shape[0])

"""
-> 149401
"""

# 事業所と事業所の読み仮名と住所を、事業所フラグを頼りに抽出してオフィスリストととして抜き出す。
# The reading kana and address of the office and office are extracted from the office flag and extracted as an office list.
office_list = df.loc[df['事業所フラグ']==1,['事業所名','事業所名カナ','事業所住所']]

print(office_list.head(10))

"""
事業所名                            事業所名カナ                     事業所住所
1        北海道テレビ放送 株式会社            ホツカイドウテレビホウソウ カブシキカイシヤ                北1条西1丁目6番地
2       富士通 株式会社 北海道支社           フジツウ カブシキガイシヤ ホツカイドウシシヤ    北2条西4丁目1番地札幌三井JPビルディング
3    損害保険ジャパン日本興亜 株式会社       ソンガイホケンジヤパンニツポンコウア カブシキガイシヤ  北1条西6丁目2番地損保ジャパン日本興亜札幌ビル
4     株式会社 朝日新聞社 北海道支社      カブシキカイシヤ アサヒシンブンシヤ ホツカイドウシシヤ    北1条西1丁目6番地さっぽろ創生スクエア9階
5      大丸 株式会社 紙包材営業本部      ダイマル カブシキガイシヤ カミホウザイエイギヨウホンブ                北3条西14丁目2番
6             株式会社 ビッグ                      カブシキガイシヤ ビツグ     南4条西7丁目6番地ビッググループ本社ビル
7      北海道トラック交通共済協同組合      ホツカイドウトラツクコウツウキヨウサイキヨウドウクミアイ              南9条西1丁目1番11号
9         北海道 札幌旭丘高等学校          ホツカイドウ サツポロアサヒオカコウトウガツコウ               旭ケ丘6丁目5番18号
10  社会福祉法人 札幌慈啓会 慈啓会病院  シヤカイフクシホウジン サツポロジケイカイ ジケイカイビヨウイン               旭ケ丘5丁目6番50号
39         社団法人 札幌市医師会                 シヤダンホウジン サツポロイシカイ                   大通西19丁目
"""

# UTF-8にエンコードして、CSVファイルとして書き出す。
# Encode in UTF -8 and export as CSV file.
office_list.to_csv("outputpy.csv",index=False,sep=",",encoding="utf-8")

print(f'{time.time() - start} sec')

"""
-> 0.9139814376831055 sec
"""

凄く快適で、速く、やりたいことが直感的にできます。

It is very comfortable, fast, and I can do what I want intuitively.

ここまでをNimのコードに直してみます。参考にさせて頂いたサイトは以下に貼り付けます。

Let's change this to Nim code. The following is the website I referred to.

Python使いで『今後はデータビジネスの現場かも』って人、NimData/Arraymancerをさわってみておこう。

NimData

Nim 文字コードを指定したファイルの読み書き

Nimで競プロをするための基本的な機能

Module nimdata

Nim module for working with CSV files

regex


Nim Code [example_nd.nim]
# NimDataモジュールをインポートします。
# Import the NimData module.
import nimdata
import nimdata/utils

import times, os

let start = cpuTime()

let dfRawText = DF.fromFile("../zonkoku/zonkoku.csv")

# take()はどうやらPandasでいうhead()の役割らしい。
# Apparently, take () is Pandas' head ().
dfRawText.take(5).show()

echo cputime() - start, " sec"

"""
60000000,1,1101,11010000,"060-0000",0,0,"北海道","ホッカイドウ","札幌市中央区","サッポロシチュウオウク",," ","(該当なし)",,,,,,,,
60840600,1,1101,11010000,"060-8406",1,0,"北海道","ホッカイドウ","札幌市中央区","サッポロシチュウオウク",," ","(該当なし)",,,,,"北海道テレビ放送 株式会社","ホ
ツカイドウテレビホウソウ カブシキカイシヤ","北1条西1丁目6番地",
60850400,1,1101,11010000,"060-8504",1,0,"北海道","ホッカイドウ","札幌市中央区","サッポロシチュウオウク",," ","(該当なし)",,,,,"富士通 株式会社 北海道支社","
フジツウ カブシキガイシヤ ホツカイドウシシヤ","北2条西4丁目1番地札幌三井JPビルディング",
60855200,1,1101,11010000,"060-8552",1,0,"北海道","ホッカイドウ","札幌市中央区","サッポロシチュウオウク",," ","(該当なし)",,,,,"損害保険ジャパン日本興亜 株式会
社","ソンガイホケンジヤパンニツポンコウア カブシキガイシヤ","北1条西6丁目2番地損保ジャパン日本興亜札幌ビル",
60860200,1,1101,11010000,"060-8602",1,0,"北海道","ホッカイドウ","札幌市中央区","サッポロシチュウオウク",," ","(該当なし)",,,,,"株式会社 朝日新聞社 北海道支社
","カブシキカイシヤ アサヒシンブンシヤ ホツカイドウシシヤ","北1条西1丁目6番地さっぽろ創生スクエア9階",

# 一回目のコンパイルと実行。
# First compile and run.
5.737 sec
# 二回目。
# Second run.
0.002000000000000002 sec
"""

データフレームにして、オペレーションをしようとしました。

I tried to do something like Pandas with a data frame.

const schema = ["spam","spam","egg","spam"]

Schema??コラムは直接見れないの? 全部手打ちなの?? ん〜。。。

Schema?? Can't I see the column directly? Are they all handmade?? Hmm...


Nim Code [example_nd2.nim]
import nimdata
import nimdata/utils

import times, os

let start = cpuTime()

let dfRawText = DF.fromFile("../zenkoku/zenkoku.csv")

const schema = [
    intCol("住所CD"),
    intCol("都道府県CD"),
    intCol("市区町村CD"),
    intCol("町域CD"),
    intCol("郵便番号"),
    intCol("事業所フラグ"),
    intCol("廃止フラグ"),
    strCol("都道府県"),
    strCol("都道府県カナ"),
    strCol("市区町村"),
    strCol("市区町村カナ"),
    strCol("町域"),
    strCol("町域カナ"),
    strCol("町域補足"),
    strCol("京都通り名"),
    strCol("字丁目"),
    strCol("字丁目カナ"),
    strCol("補足"),
    strCol("事業所名"),
    strCol("事業所名カナ"),
    strCol("事業所住所"),
    floatCol("新住所CD")
]

let df = dfRawText.map(schemaParser(schema, ','))

df.toCsv("test.csv")

echo cputime() - start, " sec"

"""
5.604 sec
"""

Responsive image

めっちゃ文字化けしてます。 文字コードが違うからしょうがないです。 ですが、本家のページに文字コードのオプションが見当たりません。

NimData

It's garbled. It can't be helped because the character code is different. But I can't find the character code option on the main page.

NimData

しょうがないので、さっきの手打ちのコラムのところも含めて、 一旦CSVから情報を読み込み、ヘッダー情報だけ変数のように表示させてテキスト保存。 UTF8に直して保存。してからやってみます。 ああ、神様仏様Pandas様。。。

It can't be helped, so including the handmade column, Once the information is read from CSV, only the header information is displayed like a variable, and the text is saved. Convert to UTF8 and save. I'll try after that. Oh, God... Pandas....


Nim Code [cp932ToUTF8.nim]
import csvtools
# regexはnimbleでインポート可能、標準装備のreでもいいよ。
# Regex module can be imported with nimble, and the standard re is fine.
import regex
# 文字コード関係で使うEncodingもインポート
# Encoding used for character coding is also imported
import encodings

# `$`という謎の文字はオブジェクトの文字列表現(まあ文字コードだと思って頂いて、)を取ってきてくれる。
# The mysterious character `$` takes a string representation of the object (You can think of it as a character code anyway).
var utf8data = readFile("../zenkoku/zenkoku.csv").`$`.convert(srcEncoding="shift_jis", destEncoding="utf-8")

# UTF8にして書き出し
# Export as UTF8
writeFile("utf8Address.csv", utf8data)

# ヘッダー情報を入れる為の空の配列(文字列が入る)を用意。
# Provide an empty array (Contain a string of characters) for header information.
var header: seq[string]

# ヘッダーのみ抽出したいので、カウンターを用意。
# I want to extract only the header, so I prepared a counter.
var count = 0

# スキーマの雛形を作る為にまた空の配列を準備
# Prepare an empty array again to create a schema template.
var schema: seq[string]

# break文(途中脱出ができる)を効かせるためにblock文を用意。
# Prepare block statement for break statement (Be able to escape halfway).
block getHeader:
    for row in csvRows("utf8Address.csv"):
        header.add(row)
        inc(count)
        if count == 1:
            break getHeader

# 型指定が面倒くさいが、できるだけ楽をしたいのでこんな有様に。しかも他のコラムに使えません。一回きりの限定商品です。。。
# I don't want to specify the model, but I want to make it as easy as possible. Moreover, it cannot be used for other columns. It is a one-off limited item.
for i, col in header:
    if i >= 0 and i <= 6:
        schema.add("intCol(" & col & ")")
    elif i > 6 and i <= 20:
        schema.add("strCol(" & col & ")")
    else:
        schema.add("floatCol(" & col & ")")

# これを標準出力からコピペして最後のカンマだけ外す。もしくはリダイレクトでテキストファイルに書き出して、コピペ。
# Copy and paste it from standard output, removing the last comma only. You can also copy and paste it by redirecting it to a text file.
for s in schema:
    var ss = s.replace(re"\(","(\"")
    echo ss.replace(re"\)","\"),")
# like This.
$ nim c -r cp932ToUTF8.nim > schema.txt

# result
"""
schema.txt

->

intCol("住所CD"),
intCol("都道府県CD"),
intCol("市区町村CD"),
intCol("町域CD"),
intCol("郵便番号"),
intCol("事業所フラグ"),
intCol("廃止フラグ"),
strCol("都道府県"),
strCol("都道府県カナ"),
strCol("市区町村"),
strCol("市区町村カナ"),
strCol("町域"),
strCol("町域カナ"),
strCol("町域補足"),
strCol("京都通り名"),
strCol("字丁目"),
strCol("字丁目カナ"),
strCol("補足"),
strCol("事業所名"),
strCol("事業所名カナ"),
strCol("事業所住所"),
floatCol("新住所CD"),
"""

これを貼り付けて再度挑戦。

Paste this text and try again.


Nim Code [example_nd3.nim]
import nimdata
import nimdata/utils

import times, os

let start = cpuTime()

let dfRawText = DF.fromFile("utf8Address.csv")

const schema = [
    intCol("住所CD"),
    intCol("都道府県CD"),
    intCol("市区町村CD"),
    intCol("町域CD"),
    intCol("郵便番号"),
    intCol("事業所フラグ"),
    intCol("廃止フラグ"),
    strCol("都道府県"),
    strCol("都道府県カナ"),
    strCol("市区町村"),
    strCol("市区町村カナ"),
    strCol("町域"),
    strCol("町域カナ"),
    strCol("町域補足"),
    strCol("京都通り名"),
    strCol("字丁目"),
    strCol("字丁目カナ"),
    strCol("補足"),
    strCol("事業所名"),
    strCol("事業所名カナ"),
    strCol("事業所住所"),
    floatCol("新住所CD")
]

let df = dfRawText.map(schemaParser(schema, ','))

# .map()はPnadasのMapメソッドのようなものです。
# ここでは、record、一列ごとに取り出して、record.projectToで列名を指定して、その列だけ取り出します。
# .filter()はPandasのquery()のようなものです。==や!=等の論理演算で条件に一致した行を取り出していきます。
# そして最後にまた列を指定して、officeオブジェクトに格納します。
# 
# map () is like the Pnadas Map method.
# In this case, record, one column at a time, and specify the column name in record.projectTo to retrieve only that column.
# filter () is like Pandas' query (). Fetch rows that match the criteria using logical operations such as == or !=.
# Finally, you specify another column to store in the office object.

let office = df.map(record => record.projectTo(事業所フラグ, 事業所名, 事業所住所, 事業所名カナ))
.filter(record => record.事業所フラグ == 1)
.map(record => record.projectTo(事業所名, 事業所住所, 事業所名カナ))

office.toCsv("office.csv")

office.toHtml("office.html")

let endTime = cputime() - start
echo endTime, " sec"

"""
Hint:  [Link]
Hint: operation successful (54394 lines compiled; 3.233 sec total; 71.852MiB peakmem; Debug Build) [SuccessX]
Hint: C:\Users\akasatanahama\py2nim\example_nd3.exe  [Exec]
7.602 sec
"""

Responsive image


Responsive image


パンダちゃん最高!
Pnadas is awesome!

文字化けもしていないし、HTMLファイルで書き出しの結果も悪くはない。 が、列が「;」で区切られていて、文字列は「”」で区切られている。 まあ、Pandasが優秀すぎるだけですね!

It doesn't get garbled, and the results of writing in an HTML file are not bad. But the row「;」and the strings are separated by「"」. Well, Pandas is just too good!


文字列操作計測
Measuring the Speed of String Operations

これまでのコードで、一々計測時間を表示させていたに気付いたでしょうか? そう、Nimを使ってみた本当の目的はPythonのコードをCythonやNumbaを使わないで Nimで書くだけでどれだけ速くなるか試してみたかったのです。

Did you notice that the previous code displayed the measurement time each time? Yes, the real purpose of using Nim is to write Python code without using Cython or Numba. I wanted to see how fast it would be to just write in Nim.

Pandasは計測結果に影響を及びませんでした。 それはPandasがNumpyで作られていることに起因するからです。

Pandas did not affect the measurement results. That's because Pandas is made with Numpy.

それでは、実際にありそうな文字列操作や計算でどのくらい差がでるのかやっていきます。

Now, let's see how much difference there is in the actual string manipulation and calculation.


後半へ続く
Follow in the second half

ここでは東京都の緯度経度の情報を使用します。 出典:大字・町丁目位置参照情報 国土交通省

位置参照情報ダウンロードサービス

We will use the latitude and longitude information for Tokyo. Source: Oaza/Machichome Location Reference Information Ministry of Land, Infrastructure, Transport and Tourism

Location Reference Information Download Service

こちらから、「全ての街区レベルを選択」と「全ての大字・町丁目レベルを選択 」にチェックを入れて、 利用規約に同意して、ダウンロードしてくだちぃ。

Check "Select all block levels" and "Select all Oaza/Machichome levels", agree to the terms of use, and download.

3MBほどのCSVファイルがダウロードされますので、これを使って、 一行目の字・丁目と近い場所をトップ10で表示させるプログラムをPythonとNimで書いて、 どちらがどの程度の速度で計算を終えるかを見ていきます。

A CSV file of about 3MB is downloaded. I'll use it to write a program in Python and Nim that will display the first line in the top 10 locations near the street. You'll see which one finishes the calculation and how fast.

もちろんPythonはNumpyもPandasも使用しません。

Of course, Python does not use Numpy or Pandas.

ただ、正直前処理がしんどいので、一旦Pandasで前処理を行って、JSONファイルにします。 それから、Pythonは辞書(リストは使うな!絶対だ!)とFor文か内包表記かMapメソッドで、 Nimは学習2日目なので、よく分かりません。ですからアホみたいにFor文でやることになりそうです。

To be honest, preprocessing is tedious, so I'll preprocess it in Pandas once and turn it into a JSON file. Then Python uses dictionaries (Don't use lists! Absolutely!) and For statements, comprehensions, or the Map method. Nim is the second day of study, so I don't know well. So it's going to be a For statement like an idiot.

それではレッツゲットスターテッド!

Let's get started!

と、いきたいところですが、今日は寝ます。悪しからず。

I want to go there, but I will go to bed today.

ちなみにネタバレで申し訳ないが、Pythonで純粋なFor文だと リストを使った方法で途中経過6000秒。。。 辞書だと途中経過120秒。 同じ行数です。 これはやヴぁい。。。

Sorry for the spoiler, but if you're looking for a pure For loop in Python, Using the list method, 6000 seconds passed. It takes 120 seconds in the dictionary. Same number of rows. This is terrible.




See You Next Page!