単体テストを学ぼう!① -自分のプログラムに自信を持ちたい-

f:id:monozukuri-bu:20191126184429j:plain

こんにちは、ふじもんです。
朝、お布団からでるのが辛い季節になってきましたね。
いつのまにか出勤時に乗る電車が前よりも20分遅くなっていました。

遅刻ギリギリ。

さてさてお話は変わりまして。

私は業務でプログラムを書くときは、分析データの整形など、分析の前処理のスクリプトが主です。
そしてプログラムを組んでいるときに常々不安を抱えています。
それは

私のプログラム、本当に合ってる???

って。
自分で組んだプログラムが想定通りの挙動をしているかいつも不安です。

毎回処理途中のデータや最終的なアウトプットをちまちまと目視で確認はしているけど、
何せプログラムで処理したいデータなんて少ないはずがない。
それはもう膨大

さて、どうしよっかな。

この不安感、なんとかして解消できないものか。

そう思い、上司に相談してみました。

上司「最近現場どう?」
私 「現場は順調なんですけど、ちょっと悩みが…」
上司「え。どしたの」
私 「自分でスクリプト書くじゃないですか。それを実行してみて、一見思い通りに動いてるように見えるんですけど、コレ本当に全てのデータに対して正しく動いてるのかなって…。」
上司「それならテストコード書けばいいんじゃない?」
私 「テストコード」
上司「…ふじもんって開発業務とかにおけるテストってどういうことするかわかる?」
私 「ばなな。」
上司「わからないなら素直に言おうか。じゃあまずテストとは何なのかを調べてみようか。そうしたら、自分のプログラムがちゃんと想定通りに動いてるか調べる方法とかもわかるかもね。」

テストってなんだ?

上司に言われるがままに、開発経験ゼロの私は早速テストについて調べてみました。
そもそも、開発業務に置けるテストには、大きく分けて以下の3種類があるようです。

単体テスト
 プログラム1つが期待した通りに動作するかテストを行う。

結合テスト
 複数のプログラムを組み合わせ、それらが一体として期待したとおり動作するかテストを行う。

・総合テスト
 システムの全機能を最初から最後まで一通り運転させ、期待したとおり動作するのかテストを行う。

ふむなるほど。
プロジェクトによってどのテストを行うかはまちまちだそうですが、上記3つは大体行われるようです。

また、テストの範囲はプログラムに限らずモジュールや関数などにも適用され、厳密には決まっていません。
システム開発ではこれらを行うことにより、仕様通りに動くことを確かめてからリリースします。

つまり・・・?

私の悩みは自分の書いたプログラム1つが正しい挙動をしているか確認したいので、
上記の3種類の中で必要なテストは「単体テスト」に当たります。

単体テストってどうやるの!!! はやく!はやくこの不安感から解放されたいんだ!!

単体テストってどうやるの?

逸る気持ちで単体テストについて調べていると、単体試験の中にも2種類あって、観点が違うもよう。

確かに、プログラムを確認する観点を決めていなかったな…
漠然と入出力を見て間違いがないかを確認していたな…
それで、確認しても不安が残っていたのか。。。

これは重大な気付きです!!!

ブラックボックステスト

テスト対象の入出力に着目し、入力に対して期待した出力になるかを検証する。

その観点は、

同値分割

起こりうる全ての事象をいくつかのグループに分け、各グループから代表値を選んでテストを行う。

境界地分析

同値分割によって分けられた各グループの境界値付近をテストを行う。

ホワイトボックステスト

テスト対象の内部構造に着目し、条件分岐や繰り返しなどの各部分を確実に検証する。

その観点は、

命令網羅(C0)

テスト対象となるプログラムの命令文それぞれが、1回以上実行されるようにテストを行う。

分岐網羅(C1)

テスト対象となるプログラムに含まれる判定条件について、真となるケース、偽となるケースそれぞれが、1回以上実行されるようにテストを行う。

条件網羅(C2)

テスト対象となるプログラムの条件文について、真となるケース、偽となるケースそれぞれが、1回以上は実行されるようにテストを行う。

なるほど!!!

ブラックボックステストは、ソースコードはどうあれ入力に対して出力が正しいかを確認して、
ホワイトボックステストは、ソースコードの中身の挙動が正しいかを確認するってことですね。

お、確かにこの観点でプログラムの挙動をチェックしていけば、「なんとなく大丈夫そう」よりは圧倒的に不安が取り除かれる気がする…!
これは不安から解放される日も近いのでは!

テスト基準のモノづくり -テスト駆動開発-

よーし、じゃあさっそくテスト…って思ったけど、テストするにもテスト対象のモノがない。
なら作れば良いじゃない。
じゃあテストをするための簡単なシステムを作ろう。
せっかくテストのことを理解するなら、テストしやすいものを作りたい。
何をどんな風に作ろうかなと調べていると、テスト駆動開発という言葉を発見しました。

テスト駆動開発(TDD:Test-Driven Development)

テストファーストという考えに基づいた開発手法。
テストファーストとは、まず本番で使用するプロダクションコードを書く前に何をテストするかを決め、テストコードを先に書くのが特徴。

そのテストファーストという考え方を組み込んだテスト駆動開発は、最低限必要なテストを積み重ねていき、少しずつ確実に実装できるそう。
そして、この手法の大きなメリットとして確実に動くコード読みやすく綺麗に書けること。

よく自分の書いたコードがあとで読めなくなったり、学生時代に「コードの汚さは頭の中の汚さ」とゼミの先生に言われていた私にとって、もうこんな最適な開発手法あったの?もっと早く知りたかった。
確実に動くコードを書けると、心理的負担も減りますしね!
他にもデバッグの時間が短くなったり、実装コードの修正があった場合に修正と無関係な部分を破壊していないかをチェックするリグレッションテストにも役立つメリットもあるようです。

まとめ

今回の記事では以下のことについて調べて記載しました。
開発におけるテストには以下の種類がある。
単体テスト
結合テスト
・総合テスト
それぞれを行うことにより、一つのシステムのバグの検証や確実性を高めてからリリースできる。

単体テストは以下の種類がある。
ブラックボックステスト
ホワイトボックステスト
それぞれを行うことにより、プログラムの入出力からソースコードの挙動までを確認することができる。

テストファーストの開発「テスト駆動開発」というものがあり、プロダクションコードよりテストコードを先に書くことにより、
確実に動くコードを綺麗に書けるメリットがある。

次回の記事ではテスト駆動開発でプログラムを実装しながら、テストについてより理解を深めていきます!

SKYWILLハッカソン2019冬の陣

今回のお題は【フレッシュ】

初めて参加される方もいらっしゃいましたが、雑談しながら楽しく開催することができました。

f:id:monozukuri-bu:20191126102250j:plain f:id:monozukuri-bu:20191126102318j:plain

チーム 1:写真共有アプリのフレーム作成

API の知識を深める
・node + express + mongodb の知識を深める

API 設計
URL メソッド 内容
/ GET 全ユーザ取得
/:id GET ユーザー取得
/ POST ユーザー登録
/:id PUT ユーザー更新
/:id DELETE ユーザー削除
$curl -X POST http://localhost:3000/ -H "Content-type: application/json"  -d '{"userid":"1","username":"sugi","password":"password"}'
{"user":{"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"sugi","password":"password"}}

$curl -X GET http://localhost:3000/ -H "Content-type: application/json"
[{"_id":"5dcfa5eadebf7f5a6f912840","userid":2,"username":"ryu","password":"password","__v":0},{"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"sugi","password":"password","__v":0}]

$curl -X GET http://localhost:3000/1 -H "Content-type: application/json"
{"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"sugi","password":"password","__v":0}

$curl -X PUT http://localhost:3000/1 -H "Content-type: application/json"  -d '{"username":"ryu"}'
{"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"ryu","password":"password","__v":0}

$curl -X DELETE http://localhost:3000/1 -H "Content-type: application/json"
使用技術

・JS
・Node
・Express
・MongoDB
・Docker

感想

・新しい言語を触って知識を深められた。
ペアプロスキルが向上した。
API の知識が深まった。

チーム 2:フレッシュな Web サービス

・あなたのフレッシュ度を判定する心理テスト
 5 つの質問に答えると、フレッシュ度を判定してくれるスグレモノ。

f:id:monozukuri-bu:20191126105907p:plain

・画像のフレッシュ度を判定する深層学習
 画像のフレッシュ度を計算してくれる。

f:id:monozukuri-bu:20191126174535p:plain

 ※画像はぱくたそ様よりお借りしました。(モデルリリース取得画像)
 photo by すしぱく / model by yumiko

・フレッシュな気持ちになれる Google Chrome 拡張機能
 背景など特定の要素だけフレッシュな色に変える魔法。 f:id:monozukuri-bu:20191126110119p:plain

使用技術

Python, Bottle, Jinja2
HTML5, JavaScript, CSS
・TensorFlow, Keras

感想

・深層学習は1時間でできた(精度無視)けど、まだフロントで躓くことが多い。
・自分の技術力を見直せた。
・ゼロからモノづくりしたことがなかったので、いい経験が出来た。

チーム 3:音楽データのパート分割・加工・合成

・最新の音楽素材分離エンジン「spleeter」を用いて、最新技術の使用を体験する。
・音声データを「ボーカル、ベース、ドラム、ピアノ、その他」の5分割をした。
・「pyaudio」ライブラリを用いて音の高さ・速度を変化させた。

f:id:monozukuri-bu:20191126180159p:plain f:id:monozukuri-bu:20191126180218p:plain

使用技術

Python
・spleeter, pyaudio

感想

・最新の技術なので記事が3つ程度しかなかったが、プログラムの構造を見て自分で考える良い機会となった。
・最新の技術を用いて音声データの分割・合成を楽しめるいい機会となった。
・ピッチの変更、instrumental や 8bit 曲の作成など個人使用の範囲で楽しめる可能性を感じた。

チーム 4:いつも通る道のりに、寄り道を加えて案内してくれるアプリ

いつもと違う道のりを歩いてみることで新しい発見を促す。

f:id:monozukuri-bu:20191126103346p:plain

使用技術

HTML5(GeoLocationAPI)
AWS(S3)
Google Maps API
・geolib
・ServiceWorker

感想

・PWA 化をするため、ServiceWorker, Manifest ファイル配置などを行ったが、うまく動かなかった。(ホーム画面に追加ボタンでホームに追加されない…)
HTML5 の位置情報を取得する API(GeoLocationAPI)が、GoogleChrome では HTTPS 接続でないと動作しなかった。

全体を通して

各チーム、発表時に動くもの、今後のベースなど作成できていて安心しました。

短い時間で何かを作ることはとても大変なことです。
チームのスキルセットを把握、役割分担し、作るものを決める。
やりたいこと、好きなことができるわけではないですが、チームとして手や頭を動かす必要があります。

苦労することもありますが、非日常的、刺激、チーム開発体験などハッカソンに参加する良さはたくさんあります。

今回は社内イベントとして開催しましたが、今後もconnpassなどで募集して社外向けイベントとして開催することもあると思います。
これを機にハッカソン参加してみようかな~と思ってくださったら幸いです。

参加してくださったみなさま、本当にありがとうございましたー!

データエンジニアとしての第一歩を終えて

f:id:monozukuri-bu:20191031163847j:plain

皆様お疲れ様です。
tsudaです。

新卒の研修を5月中旬に終え, そのまま現場に配属され早5か月が立ちました。
配属された現場も丁度今月で離任ということで, 研修と現場での業務について所感を述べます。

自己紹介

大学ではグラフ理論, 数理計画法, 統計学, 数値解析等を学んでいました。
プログラミング経験はCとPython, どちらも基礎文法レベルですが習得しました。

現場ではデータ分析ソフトウエアの環境構築, 運用に携わっていました。

研修で学んだこと

主にPythonを用いて"データ分析"の一連の流れをハンズオンで学びました。
手法の中身について詳しく掘り下げるというより, とにかく"データ分析"を流れでやってみるという部分に重点を置いており, 体系的に学ぶことでデータ分析業務に必要な能力が研修を学ぶ前よりも明確にすることができました。

その他にはLinuxコマンドの基礎を学び, 現場では構築作業の際に非常に役に立ちました。

携わった業務

PostgreSQLのユーザ権限設定

PostgreSQLを用いてユーザー, データベースの作成, 削除も行っていましたが,
「どのユーザーがどのデータベースのどのスキーマのテーブルを参照できればいいのか」
というアクセス権限の設計を主に行っておりました。
トライ&エラーで試すことができる環境もあり, パズルのようで楽しかったです。

運用ツールの作成

主に下記のツールの作成&単体試験を行いました。

bash:データベースのバックアップ, バキューム
VBAcsvファイルからグラフを自動生成
PowerShell:S3からテーブルデータ取得, インポート

現場で学べたこと

5か月と短い期間であったにもかかわらず, 上に述べたように様々な体験をすることができました。
これらの体験を通して重要さを痛感し勉強になったものが以下の2つでした。

わかりやすいコードを書く

現場に入るまでに書いたスクリプトは基本的にその日の内に完結するので, 最低限必要な動作を保証したうえで変数や関数の名前, コメントによる動作の説明等は便宜的に書いていました。
しかし現場ではそれが通用しませんでした。

後日仕様変更が起きていざ修正しようと思った時に, 謎の変数"s"や, なにかのユーザを取ってきているであろう"get_user"関数がなんの説明もなしに置かれているのです。
結局そのコードを理解するのに, 作成するのと同じくらいの時間をかけてしまいました。
それ以来, 引き継ぐ相手の為, 何より自分の為に後から見ても理解しやすいコードを書くことを意識するようになりました。

再現性のある手順書を書く

基本的に手順書というものは, 誰かに引き継ぐ, 共有するために存在しています。
「その手順書を読めば, 誰もが同じ結果を再現できる」こと,
つまりは再現性が必要ということが, 手順書の作成を通してわかりました。

次への意気込み

「知識とは球体である」と誰かは言いました。

知識を蓄えると球は膨らみ球の体積, つまり「知っていること」は増えるが,  
球の外側と触れる面である表面積, つまり「知らないこと」も増えていく。

研修, 現場を通して様々な「知らないこと」を知ることができました。
次の現場でも今の知識と技術を生かし, 増やし, 成長していきたいと思います。

データ分析エンジニアとしての第一歩を歩んだ所感

f:id:monozukuri-bu:20191018151721j:plain

こんにちは、ちょねです。
データ分析系の研修を経て、実際に業務としてデータ分析を行ってきましたので、所感をまとめたいと思います。

自己紹介

大学では教育学と情報系を専攻していました。
プログラミング経験としては、統計や機械学習の勉強をしてきた過程でPythonを扱ってきました。

現在は機械学習技術を用いたデータ分析の業務を行っています。
好きな言葉は「苦手じゃない。未熟なだけ。」です。

研修で学んだこと

入社後の研修では、GUIツールとPythonを用いたデータ分析や 機械学習の基礎を学びました。
その他、Linuxコマンド入門、Flaskを使った簡単なWeb開発を行いました。

この研修のよかったところは、今まであまり扱ったことのない 画像やテキストなどのデータの分析を学習できたことです。
研修の後半では、出された課題がわからない場面が多々ありましたが、ネットで調べれば大体同じような場面で躓いている人が質問していて、解決の糸口になるという感覚もつかむことができました。

求められたスキル

初めての業務は、機械学習モデルへ入力するデータの前処理がメインだったため、データを扱える技術が必要でした。

私はPythonをやってきたことからPandasでデータを扱っており、以前より使いこなせるようになっていく実感がありました。
特にgroupbyやapplyをよく使用していました。

また、機械学習の前処理の理解も必要でした。
データの処理方法によって予測精度が変わってくるため、どのような前処理を行い、どのような特徴量を用いれば精度が向上するのかを考えながら業務を行う必要がありました。

不足していたスキル

1つめは、データ処理の技術です。
実際の業務では、整ったデータから分析をスタートできるわけではないと痛感しました。
いくつかのデータを結合して思い通りの形に整形できるように、実際に手を動かして慣れておいたほうが良かったと思いました。

2つ目は、機械学習の評価指標を詳しくなっておくべきでした。
精度といえばAccuracyだという感覚だったので、Precision、Recall、F値、AUC、ROC曲線などの評価指標の知識があれば、 より理解がスムーズだったかと思います。

成長したポイント

業務を通じて私が学んだことは、自分本位に分析を進めてはならないことです。
こうすれば精度が上がるのではないかと推測して進んでいくのはお客様の要望と乖離していく可能性があるため、慎重になるべきです。

加えて、なぜその方法をとったのか説明できなければ、価値は生まれにくいです。
そのため、お客様の要望を満たしつつ、データ分析の専門家ではない人に対しても、分析の結果や過程について、十分な説明ができることが重要であると学びました。

意気込み

入社後、初のデータ分析エンジニアとしての業務を行ったことで、一歩前進することができたと感じています。
業務内では、自分で勉強するだけでは到底得られないような知識を身に付けることができ、良い刺激になりました。

データ分析に関してはまだまだ未熟なので、これからも精進していきます。

Oracle Database ロックについて

f:id:monozukuri-bu:20191016173821j:plain

みちです。
Oracle Databaseのロックについて勉強していきたいと思います。

ロック

 複数のユーザーが同時に書き込み処理を行うと、矛盾が起きてしまう場合があります。
その矛盾が起こらないようにOracleサーバーでは、書き込み処理に対してロックがかかります。
変更の対象の行ごとに排他ロックをかけてからデータの変更処理が行われ、トランザクションが終了したときに解除されます。 ロックがかかっている行に対して変更処理を行おうとする場合、解除されるまで待機し、そのあとに変更処理が行われます。

実際に試してみましょう。
id、name、num、という列を持った表を作成します。

CREATE TABLE ex1
(
    id NUMBER(2),
    name VARCHAR2(100),        
    num NUMBER(10)
);

INSERT INTO ex1 VALUES (1, 'りんご', 100); 
INSERT INTO ex1 VALUES (2, 'バナナ', 50); 
select * from ex1;
ID NAME NUM
1 りんご 100
2 バナナ 50

ここまでのsqlを打つと結果として上の表のような結果が出ます。

USER Aでデータ確認をします。

SELECT * FROM ex1
WHERE ID = 1;
ID NAME NUM
1 りんご 100

USER Bでデータを確認します。

SELECT * FROM ex1
WHERE ID = 1;
ID NAME NUM
1 りんご 100

USER AがここでID=1の行に排他ロックをかけてデータを更新します。

UPDATE ex1
SET NUM = 25
WHERE ID = 1;

USER BはID=1の行に排他ロックをかけようとしますが、USER AがID=1の行に排他ロックをかけているため、待機状態になります。

UPDATE ex1
SET NUM = 200
WHERE ID = 1;

USER Aでデータを確認します。
NUM列が変更されていますね。

SELECT * FROM ex1
WHERE ID = 1;
ID NAME NUM
1 りんご 25

USER Aでロックを解除します。

COMMIT;

USER Bでデータを確認します。

SELECT * FROM ex1
WHERE ID = 1;
ID NAME NUM
1 りんご 200

USER Aの排他ロックが解除されたため更新処理がされました。

この状態で、USER Aでデータを確認します。
USER Bの変更は確定していないため、USER A側ではUSER Bの変更内容が入っていません。

SELECT * FROM ex1
WHERE ID = 1;
ID NAME NUM
1 りんご 25

USER Bで変更内容を確定し、ロックを解除します。

COMMIT;

USER Aでデータを確認してみると、USER Bが行った変更が反映されていることが確認できます。

SELECT * FROM ex1
WHERE ID = 1;
ID NAME NUM
1 りんご 200

まとめ

 今回はロックついて勉強しました。
身近に使われているシステムの仕組みを知れてよかったです。

Python開発の初現場を終えて、学んだこと

f:id:monozukuri-bu:20191016153457j:plain

4月に新卒として入社し、先輩エンジニアと一緒にPythonでの開発を経験しました。
そこで学んだことを整理したいと思います。

開発環境

・言語
 Python 3.7

・ORM
 SQLAlchemy

・データベース
 PosgreSQL

コーディングにはVSCodeを使用しました。

プロジェクトで携わった部分

プロジェクトでは、比較的納期が長く、簡単な以下の機能開発を担当させていただきました。

・DBから特定のデータを取得、整形し、所定のフォーマットでエクスポートする機能
・エクスポートされたデータを、別環境のDBにインポートする機能

機能自体はシンプルですが、最大で同時に約5000万件ほどのデータを扱うため、Pandasなどで処理できない量ではありませんが、少なくもないという絶妙なデータ量でした。

苦労した点

設計には関わっていなかったため、設計書の内容をそのまま実装しただけでしたが、処理自体はそこまで複雑なものではありませんでした。
ただ、データ量が想像していたよりも多く、メモリ不足や速度の面で試行錯誤が必要になったことが苦労しました。

データのエクスポート機能

まずは自力で実装

設計書には実現したい機能と処理の流れが記載されていましたので、Pandasとfor文で実装しました。
この段階では、自分一人で要求されていた機能を実装することができました。
ただ、いざ動かしてみると、1日分のデータをエクスポートするのに約30分掛かっており、実際の利用シーンで想定されるのは1ヶ月〜3ヶ月分とのことだったので、この処理速度では話になりませんでした。

原因の特定と解決策

というわけで先輩エンジニアに相談し、コードの改良に入りました。
まずはじめに、pstatsというライブラリを使用してコードのボトルネックを特定する作業に入りました。
その結果、やはりfor文の使い方が適切でなく、無駄にループしていたりネストが深いのではないかということがわかりました。

この課題の解決策として、Pandasの関数を使用して可能な限り列や行に対してまとめて処理するようにと先輩エンジニアからアドバイスを頂きました。
この修正を行うことで処理時間を大きく減らすことに成功し、1か月分で約15分という許容範囲に収めることができました。

また、ロジックの共通化やコメントの追加なども、ご指摘を頂いていました。
修正作業と合わせて指摘を反映していくと、自分が書いたコード量の約半分の記述で、より高速に動作し、理解しやすいコードに変身しました。

データのインポート機能

まずは自力で実装

こちらはエクスポート機能と同じ要領でまとめて処理するように実装し、データを整形する部分ではそれほど時間は掛かりませんでした。
ただ、データをテーブルにINSERTする部分が2時間近く掛かる状態になってしまいました。
このINSERT処理は、SQLAlchemyというORMを介して行っていました。

課題の原因と解決策

先輩エンジニアに状況を伝えたところ、INSERTを実現する処理には様々な方法があるので、色々試してみるとよいとのことでした。

最初に考えたのは、Pandasのdf.to_sqlを使用してDataFrameを直接テーブルにINSERTする方法です。
これでも処理時間は大幅に短くなりましたが、それでもまだ1時間近く掛かっていました。
他にも色々と試行錯誤を重ねた結果、最終的にはORMは使用せず、Psycopg2を使用してPosgreSQLのcopy_fromでINSERT処理を行うことにしました。
その結果、2時間掛かっていた処理を30分程度まで短縮することができ、許容範囲内に収めることができました。

プロジェクトの経験を通して勉強になったこと

・ロジックの共通化
・コードが短くなるアルゴリズムの組み方
・第3者が読んで理解しやすいコードの書き方
 (PEP8などのコード規約や変数名の定義、コメントの記述)
・テストコードや仕様書の書き方

今まではプログラムが動けば問題ないと思っていました。
しかし、ロジックをできる限り関数で共通化し、コードをわかりやすくしておくと仕様変更があった際にも比較的容易に対処でき、かつ保守性も高まります。
分かりやすいコードを書くということが実際の業務において必要不可欠だということが、今回の案件で身をもって理解できました。

またアルゴリズムの組み方だけでなくテストや開発書類の書き方についてなども、スキルの高いエンジニアの方々にフォローしてもらったことが非常に勉強になりました。

Python:リストとNumpy配列の違い

f:id:monozukuri-bu:20191007172946j:plain

ふっちーです。
今回は戻ってPythonです。

研修で触ったNumpyについてまとめたいと思います。

目次

・Numpyの用途
・Numpyのリスト
・リストと配列の変換
・スライシング
・データ型は統一せよ
・四則演算

環境

今回はGoogle Colabolatory(Colab)を使用しました。
colab.research.google.com 本来Numpyはインストールが必要なモジュールです。
しかし、colabは機械学習を目的に作られているので、インポートするだけで使用できます。

Numpyとは

そもそもNumpyとは何でしょうか。
Wikipedia先生に頼ってみます。

Pythonは動的型付け言語であるため、プログラムを柔軟に記述できる一方で、純粋にPythonのみを使って数値計算を行うと、ほとんどの場合C言語Javaなどの静的型付き言語で書いたコードに比べて大幅に計算時間がかかる。そこでNumPyは、Pythonに対して型付きの多次元配列オブジェクト (numpy.ndarray) と、その配列に対する多数の演算関数や操作関数を提供することにより、この問題を解決しようとしている。NumPyの内部はC言語 (およびFortran)によって実装されているため非常に高速に動作する。したがって、目的の処理を、大きな多次元配列(ベクトル・行列など)に対する演算として記述できれば(ベクトル化できれば)、計算時間の大半はPythonではなくC言語によるネイティブコードで実行されるようになり大幅に高速化する。さらに、NumPyは BLAS APIを実装した行列演算ライブラリ (OpenBLAS, ATLAS, Intel Math Kernel Library など)を使用して線形代数演算を行うため、C言語で単純に書いた線形代数演算よりも高速に動作しうる

NumPy - Wikipedia

な、なるほど!
つまり、普通にPythonで書くと遅いけど、Numpyを使えば内部でC言語Fortranが動作して高速に演算ができる、ということですね。

NumpyはPythonのモジュールとだけあってPythonと似たように動かすことができます。
しかし、異なる部分も多くありPythonに似てる分ややこしいこともあります。
今回はこのNumpyをPythonとどう違うのかを見ていきましょう。

Numpyのリスト

早速Numpyに触ってみましょう。
NumpyはPythonで言うところのリストによく似ています。
それもそのはずでNumpyはリストと同じ構造の配列を基本としています。

しかし、リストとは異なり、Numpyには数学の行列の概念が存在します。
Numpyでは行を「次元数」、列を「要素数」など表記する場合があります。
また、Pythonの組み込み関数で使えるものを「リスト」、Numpyで使われるものを「配列」または「多次元配列」と呼びます。
今回は上記の書き方で統一します。

では、早速Numpyに触れていきましょう。

import Numpy as np

x = np.array([[1, 2, 3], [4, 5, 6]])#arrayで具体的に配列を作る
print(x)
# [[1 2 3]
#  [4 5 6]]

これでNumpyの配列が作成できました。
Numpyではこれをndarrayというデータ型として扱います。
list型とは異なるので、間違えないようにしましょう。

type(x)
# Numpy.ndarray

配列の作り方は他にもあります。

arange関数

y = np.arange(0, 12).reshape(2,6)#reshape(n,m)でn×mの行列に変換
print(y)
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]]

0から11の配列ができました。
arange関数はrange関数のように配列を作ることができます。

linspace関数

z = np.linspace(1, 25, 12).reshape(3,4)
print(z)
# [[ 1.          3.18181818  5.36363636  7.54545455]
#  [ 9.72727273 11.90909091 14.09090909 16.27272727]
#  [18.45454545 20.63636364 22.81818182 25.        ]]

1から25の間を12等分した値の配列ができました。
linspace関数は始点と終点を決めて、その間を好きな数で等分した作ることができます。
また、linspace関数はarange関数とは違って、終点を含める点に注意しましょう。

リストと配列の変換

PythonのリストとNumpyの配列は互いに変換することができます。
最初に作った多次元配列のxをリストにしてみましょう。

多次元配列からリストへ

x_list=x.tolist()
print(x_list)
print(type(x_list))
# [[1, 2, 3], [4, 5, 6]]
# <class 'list'>

to_list()を使うことでこのようにリストに変換できます。

今度はx_listを多次元配列に変換して、元に戻るか試してみましょう。

リストから多次元配列へ

x_ndarray = np.array(x_list)
print(x_ndarray)
print(type(x_ndarray))
# [[1 2 3]
#  [4 5 6]]
# <class 'Numpy.ndarray'>

多次元配列を作ったarray関数で変換することができました。
最初に作ったxと同じ結果になっているのが分かります。

スライシング

Numpyでもスライシングは可能です。

print(x[1][:2])
# [4 5]

また、このような書き方も認可されています。

print(x[1, :2])
# [4 5]

行と列の順番は最初の例と変わりありません。

また、Numpyでは配列という特性から行と列という概念を持っていますと説明しました。
そのため列でスライシングすることができます。

print(x[:, 2])
# [3 6]

それぞれの行のインデックス値が2の要素を抜き出しました。
このようにNumpyでは縦横ともにスライシングできます。

当然ですが、リストはできません。

print(x_list[:, 1])
# TypeError: list indices must be integers or slices, not tuple

リストではそもそも[n, m]でスライシングできないため、列の要素を抜き出すといったことはできません。
どうしてもというなら以下の方法があります。

a = [row[2] for row in x_list]
print(a)
# [3, 6]

データ型は統一せよ

Numpyの多次元配列ではPythonのリストと違って要素のデータ型をすべて統一する必要があります。
xに文字列を入れてみましょう。

x[0][1]='g'
# ValueError: invalid literal for int() with base 10: 'g'

intに変換できないとエラーが出てきました。
これは多次元配列xのデータ型がintで固定されているからです。
配列のデータ型はdtypeで調べることができます。

print(x.dtype)
# int64

zでもやってみましょう。

z[0]='g'
# ValueError: could not convert string to float: 'g'

こちらもstrはfloatにできませんとエラーが出てきました。
このように配列のデータ型は統一する必要があります。

一方リストにはそのような制約はありません。

x_list[0][1]='g'
print(x_list)
# [[1, 'g', 3], [4, 5, 6]]

四則演算

Numpy配列の四則演算は各要素ごとにその計算が行われます。
また、Numpyでは配列の形が異なっていても、一定の条件で四則演算することができます。

print(x*3)
# [[ 3  6  9]
#  [12 15 18]]

このように配列ではない相手でもすべての要素に対して計算を行います。
これはスカラー倍と呼ばれています。

次に配列同士で計算してみましょう。

b = np.array([1,2,3])
print(x+b)
# [[2 4 6]
#  [5 7 9]]

列数は両方とも3ですが、bは次元数が1で、xは次元数が2ですが計算できました。
このように次元数が異なる場合は、次元数が少ないほうの配列が多いほうに次元数を合わせます。
つまり、aは計算する瞬間だけ
[[1,2,3]
[1,2,3]]
となり、それぞれの要素の積を計算できたということです。

最後に列数が異なる場合の計算も見てみましょう。

c = np.array([[5], [7]])
print(x-c)
# [[-4 -3 -2] 
#  [-3 -2 -1]]

こちらもcが
[[5,5,5]
[7,7,7]]
のようになり、計算することができました。

このように計算するために配列の形を合わせるのをブロードキャスト機能といいます。

ちなみに、このような場合は計算できません。
+ (n,m)と(n,l)

print(x-y)#(2,3)と(2,6)
# ValueError: operands could not be broadcast together with shapes (2,3) (2,6) 

これはブロードキャスト機能が適用されるのは条件があるからです。
それは計算に使える配列は次元数か要素数が1か最大値と同じ場合のみと限られているからです。
要するに次元数が2において計算できる配列の形は
・(n, m)と(n, m)
・(n, m)と(n, 1)
・(n, m)と(1, m)
・(n, 1)と(1, m)
・(n, m)とk(実数)
だけになります。

また、これは行列との計算とは異なるということに注意しましょう。
例えば、(a, b) 行列と (c, d) 行列の乗算を行う場合、b=cでないと計算できません。
Numpy配列で行列の計算を行う場合は、dot関数を使いましょう。

d = np.array([[3,3],[3,3],[3,3]])
print(np.dot(x,d))
# [[18 18]
#  [45 45]]

リストに対して同じようにすると以下のようになります。

print(x_list*3)
print(x_list+3)
# [[1, 'g', 3], [4, 5, 6], [1, 'g', 3], [4, 5, 6], [1, 'g', 3], [4, 5, 6]]
# TypeError: can only concatenate list (not "int") to list

最後に、Numpyの計算速度が速いことを確かめてみましょう。

d = np.arange(1,1000) 

%timeit sum(d)
%timeit np.sum(d)
# 10000 loops, best of 3: 93.9 ?s per loop
# The slowest run took 21.44 times longer than the fastest. This could mean that an intermediate result is being cached.
# 100000 loops, best of 3: 4.3 ?s per loop

上がPythonのsumで計算した場合の時間で、下がNumpyのnp.sumで計算した場合の時間です。
このようにNumpyの方が速いとわかります。

まとめ

Numpyの配列とPythonのリストの違いをまとめました。
Pythonに似ているからこそ、覚えやすい面もあれば、混乱する面もあります。
しっかり細かな違いを把握していきましょう。