Kuzunoha-NEのブログ

pythonの勉強中。

【Python】pandasを使ってみる

こんばんは、葛の葉です。

ここ最近はPyQというサービスを使ってPythonの勉強をしています。環境構築とかがなくても、Webブラウザ上ですぐに学べるのがいいところだと思います。月3000円とネットフリックスと言った娯楽サービスの月額料金と比べるとお高い感じがしますが、参考書1冊分だと思えば安い方だと思います。私はどちらかというと手を動かして覚えたい方ですので、こういったコードを書きながら学べるサービスは非常に良いと思いました。

pyq.jp

さて、私は現在、データ分析のコースをプレイしております。そのうち、Pandasを使った勉強をしております。勉強したことを少し載せます。

こういうCSVを作る

kattakiroku.csv

kiroku_id,date
1,20180101
2,20190208
3,20190515
4,20190623
5,20191111

sale.csv

shohin_id,name,price
1,カレールー,100
2,にんじん,210
3,じゃがいも,100
4,メンマ,580
5,牛肉,90
6,玉ねぎ,130

nanikatta.csv

kiroku_id,shohin_id
1,1
1,3
1,5
2,2
2,4
2,5
2,6
3,1
3,2
4,3
4,4
4,4
5,1
5,6

pandas.read_csvcsvからデータを取得する

pandasを使って上記のCSVを読み込むと以下のようになります。

import pandas as pd

sale = pd.read_csv('sale.csv')
kattakiroku = pd.read_csv('kattakiroku.csv')
nanikatta = pd.read_csv('nanikatta.csv')

import pandas as pdについて、pdとはpandasが推奨する省略形式のようです。

pd.read_csv(CSV_FILE)とすることでCSVの中身をpandasのDataFrameというオブジェクトとして読み込むことが出来ます。

DataFrameは2次元データを管理するオブジェクトです。1次元データの場合はSeriesというオブジェクトで管理されます。

pandas.pydata.org

pandas.mergeで結合する

keyを使ってデータを結合することができます。結合の対象となるものは同じカラム名があるときの場合のようです。on引数でkeyを設定することも出来ます。

merged_df = pd.merge(nanitabeta, sale)
print(merged_df)

=>
    kiroku_id  shohin_id   name  price
0           1          1  カレールー    100
1           3          1  カレールー    100
2           5          1  カレールー    100
3           1          3  じゃがいも    100
4           4          3  じゃがいも    100
5           1          5     牛肉     90
6           2          5     牛肉     90
7           2          2   にんじん    210
8           3          2   にんじん    210
9           2          4    メンマ    580
10          4          4    メンマ    580
11          4          4    メンマ    580
12          2          6    玉ねぎ    130
13          5          6    玉ねぎ    130

カラムに対し1レコードがもっている要素の数が多いように見受けられます。これはPandasがもっているindexというものになります。下記のレコードの一番左の0indexです。indexは好きに設定することも出来ます。

    kiroku_id  shohin_id   name  price
0           1          1  カレールー    100

DataFrame.queryでフィルタリング

kiroku_idが1のデータを取り出すようにフィルタリングします。pandas.queryを使うことで、直感的に操作できます。

merged_df_1 = merged_df.query('kiroku_id == 1')
merged_df_1_price = merged_df_1.sum()['price']
print(merged_df_1_price)

=>
290
<class 'int'>

merged_df.query('kiroku_id == 1')のようにDataFrame.query('QUERY文')を書くとフィルタリングした結果が返されます。

print(merged_df.query('price >= 200'))
=>
kiroku_id  shohin_id  name  price
7           2          2  にんじん    210
8           3          2  にんじん    210
9           2          4   メンマ    580
10          4          4   メンマ    580
11          4          4   メンマ    580

print(merged_df.query('price >= 580 | price <= 100'))
=>
kiroku_id  shohin_id   name  price
0           1          1  カレールー    100
1           3          1  カレールー    100
2           5          1  カレールー    100
3           1          3  じゃがいも    100
4           4          3  じゃがいも    100
5           1          5     牛肉     90
6           2          5     牛肉     90
9           2          4    メンマ    580
10          4          4    メンマ    580
11          4          4    メンマ    580

print(merged_df.query('name == "牛肉"'))
=>
kiroku_id  shohin_id name  price
5          1          5   牛肉     90
6          2          5   牛肉     90

もっと出来ることありそう

pandasを触っててやりやすいと思うのは直感的に操作できることだと思いました。mergeもqueryも直感的でわかりやすいです。

【Python】1年近くプログラマーやってFizzBuzzの作り方が変わったのさ

こんばんは、葛の葉です。

以前までの作り方

def fizuubuzz(num):
    if num % 15 == 0:
        return 'FizzBuzz!'
    elif num % 3 == 0:
        return 'Fizz!'
    elif num % 5 == 0:
       return 'Buzz!'
    else:
        return str(num)

これからの作り方

ディレクトリを作成する。

├── /app
└── /test

このようにテストと本番を別ける。

testアプリケーションを作成する。

pytestで初期実行するconftest.pyを以下のように作成する。

# conftest.py
import os
import sys
sys.path.append(os.path.abspath(os.path.dirname(
    os.path.abspath(__file__)) + '/../app/'))

これは、テストを実行するときのディレクトリの目線をappにするものです。 import等、ディレクトリを読み込むときはappディレクトリがスタートになります。 カレントディレクトリがappになるといった感じです。

├── /app
└── /test
    └── conftest.py

列挙型で定数を作る

pythonには列挙型であるenumというものがあります。これの整数列挙型であるIntEnumというものを使用します。

app/Numbers.pyというpythonプログラム作成し、以下のようにします。

# Numbers.py
from enum import IntEnum


class Numbers(IntEnum):
    FIZZ = 3
    BUZZ = 5
    FIZZBUZZ = FIZZ * BUZZ

これの説明はtest_Numbers.pyというテストプログラムを作成します。

# test_Numbers.py
from Numbers import Numbers


def test_Numbers():
    assert 3 == Numbers.FIZZ
    assert 5 == Numbers.BUZZ
    assert 15 == Numbers.FIZZBUZZ

testディレクトリをカレントディレクトリにしてから、pytestと実行すれば1 passed in 0.** secondsと表示されるはずです。

======================== test session starts =========================
platform linux -- Python 3.6.6, pytest-3.8.1, py-1.6.0, pluggy-0.7.1
rootdir: /home/kuzunoha/sand_box/test, inifile:
plugins: pylama-7.6.6
collected 1 item                                                     

test/test_Numbers.py .                                         [100%]

====================== 1 passed in 0.02 seconds ======================

Numbers.FIZZ整数3とアサート出来ます。

Numbers.BUZZ整数5とアサート出来ます。

Numbers.FIZZBUZZ整数15とアサート出来ます。

これらNumbers定数のように扱うようにします。

├── /app
│   └── Numbers.py
└── /test
    ├── conftest.py
    └── test_Numbers.py

FizzBuzzを作る。

まずは空のapp/FizzBuzz.pyと空のtest/test_FizzBuzz.pyを作成します。

├── /app
│   ├── FizzBuzz.py
│   └── Numbers.py
└── /test
    ├── conftest.py
    ├── test_FizzBuzz.py
    └── test_Numbers.py

そうしてFizzBuzz.pyを作成します。

# FizzBuzz.py
from Numbers import Numbers


class FizzBuzz(object):
    def checker(self, insert_number):
        return 'Fizz!'

test_FizzBuzz.pyを作成します。

# test_FizzBuzz.py
import pytest

from FizzBuzz import FizzBuzz


@pytest.fixture()
def fizz_buzz():
    # インスタンスを作成
    fizz_buzz = FizzBuzz()
    return fizz_buzz


def test_checker(fizz_buzz):
    assert 'Fizz!' == fizz_buzz.checker(3)

pytest test_checker.pyと実行してパスすればしっかりインポートが成功しています。さて、ここまで作ってようやく、FizzBuzz.pyの実装をいろいろ考えられます。今回はFizzBuzzクラスのcheckerという関数から文字列を返してもらえばいいように考えます。

def test_checker(fizz_buzz):
    assert 'Fizz!' == fizz_buzz.checker(3)
    assert '2' == fizz_buzz.checker(2) # 追加

今の状態ではテストは通りません。テストを通すようにします。

# FizzBuzz.py
from Numbers import Numbers


class FizzBuzz(object):
    def checker(self, insert_number: int) -> str:
        if insert_number % Numbers.FIZZBUZZ == 0:
            return 'FizzBuzz!'
        elif insert_number % Numbers.FIZZ == 0:
            return 'Fizz!'
        elif insert_number % Numbers.BUZZ == 0:
            return 'Buzz!'
        else:
            return str(insert_number)

pytestを行う

===================== test session starts ======================
platform linux -- Python 3.6.6, pytest-3.8.1, py-1.6.0, pluggy-0.7.1
rootdir: /home/kuzunoha/sand_box/test, inifile:
plugins: pylama-7.6.6
collected 2 items                                              

test/test_FizzBuzz.py .                                  [ 50%]
test/test_Numbers.py .                                   [100%]

=================== 2 passed in 0.03 seconds ===================

テストする項目を増やしてみる。

# test_FizzBuzz.py
import pytest

from FizzBuzz import FizzBuzz


@pytest.fixture()
def fizz_buzz():
    # インスタンスを作成
    fizz_buzz = FizzBuzz()
    return fizz_buzz


def test_checker(fizz_buzz):
    assert 'Fizz!' == fizz_buzz.checker(3)
    assert '2' == fizz_buzz.checker(2)
    assert 'Buzz!' == fizz_buzz.checker(5)
    assert 'FizzBuzz!' == fizz_buzz.checker(15)
    assert 'FizzBuzz!' == fizz_buzz.checker(30)
======================== test session starts =========================
platform linux -- Python 3.6.6, pytest-3.8.1, py-1.6.0, pluggy-0.7.1
rootdir: /home/kuzunoha/sand_box/test, inifile:
plugins: pylama-7.6.6
collected 2 items                                                    

test/test_FizzBuzz.py .                                        [ 50%]
test/test_Numbers.py .                                         [100%]

====================== 2 passed in 0.03 seconds ======================

もっともっと勉強が必要

がんばらないと

【Python】pycryptomodexのインストール

環境

Windows 10 home
Python3.6.5

Build Tools for Visual Studio 2017インストール

visualstudio.microsoft.com

画面下部にいってTools for Visual Studio 2017を押下してBuild Tools for Visual Studio 2017をダウンロード + インストール

pycryptoのpipインストール

pycrypto

pip install pycryptoでインストールに失敗する

import Cryptoで出来ない

pycryptomode

pip install pycryptomodeでインストールできる

import Cryptoで出来ない

pycryptomodex

pip install pycryptomodexでインストールできる

import Cryptoで出来ない

import Cryptodomeで動いた!

pycryptoは死んでいる

tl;dr: PyCrypto is dead. Switch to PyCryptodome (GitHub: https://github.com/Legrandin/pycryptodome PyPi: https://pypi.python.org/pypi/pycryptodome). Do not report any new bugs here!

github.com

【DockerCompose】Volumesをうまく扱う

こんばんは、葛の葉です。

前略、MySQLのデータを永続化しようと、DockerComposeのvolumesの箇所で"自分のディレクトリ:/var/lib/mysql"と記述したらMySQLがOSがどったらこったらとエラーを吐いて動きませんでした。

PCはWindows10Homeです。Docker上でMySQLを動かすと、動かしているときはLinuxだけど、保存先がwinになってる。MySQLコンテナは困惑して轟沈した、というわけのようです。

そもそもデータを永続化出来ればいいのであって、windowsにマウントする必要はないです。コンテナを落としても、新たにコンテナが立ち上がった時に同じデータを引き継いでくれればそれで良いです。

というわけで、コンテナのデータをDocker内のvolumeにマウントさせるということをやっていこうかなぁと思います。

とても参考になったサイト

qiita.com

Version

Docker Toolboxを使用しています。

# docker version
Client:
 Version:       18.03.0-ce
 API version:   1.37
 Go version:    go1.9.4
 Git commit:    0520e24302
 Built: Fri Mar 23 08:31:36 2018
 OS/Arch:       windows/amd64
 Experimental:  false
 Orchestrator:  swarm
Server: Docker Engine - Community
 Engine:
  Version:      18.09.2
  API version:  1.39 (minimum version 1.12)
  Go version:   go1.10.6
  Git commit:   6247962
  Built:        Sun Feb 10 04:20:28 2019
  OS/Arch:      linux/amd64
  Experimental: false

# docker-compose version
docker-compose version 1.20.1, build 5d8c71b2
docker-py version: 3.1.4
CPython version: 3.6.4
OpenSSL version: OpenSSL 1.0.2k  26 Jan 2017

docker-compose.ymlではこんな感じ。

今回はMySQLとRedisもやってみた。

version: "3"
services:
    mysql-svc:
        image: mysql:5.7.25
        environment:
            MYSQL_DATABASE: db
            MYSQL_ROOT_PASSWORD: secret
            TZ: "Asia/Tokyo"
        volumes:
            - "mysql_data:/var/lib/mysql"

    redis-svc:
        image: redis:5.0.3
        environment:
            TZ: "Asia/Tokyo"
        volumes:
            - "redis_data:/data"

volumes:
    mysql_data:
    redis_data:

servicesと同じレベルにvolumesというものが書かれています。これはDockerComposeを通じてvolumeというものを作成します。

volumeの機能自体はDockerComposeのものではなくDockerの機能です。また、複数のオプションが存在しています。色々と調べてみてくださいな。

docker-compose up -dとすれば以下のようになります。

$ docker-compose up -d
Creating network "docker_test_default" with the default driver
Creating volume "docker_test_mysql_data" with default driver
Creating volume "docker_test_redis_data" with default driver
Creating docker_test_mysql-svc_1 ... done
Creating docker_test_redis-svc_1 ... done

docker_testっていうディレクトリ上で起動しているのでプレフィックスがこうなってます。

MySQLにデータを入れる

mysql-svcサービスでmysqlクライアントを起動してみます。

docker-compose exec mysql-svc mysql -p

パスワードはsecretです。

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.25 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

use db;としてdbを移動し、適当にhogehogeテーブルでもつくり、適当に値を入れます。ちなみになんでdbっていうデータベースが存在しているかというと、Dockerのmysqlイメージがコンテナを生成するときに、そのコンテナがMYSQL_DATABASEという環境変数の値の名前のデータベースを作成するからです。

CREATE TABLE hogehoge (id int, name varchar(40), price int);

INSERT INTO hogehoge VALUES(1, "LION", 200);

INSERT INTO hogehoge VALUES(2, "BAER", 180);

SELECT * FROM hogehoge;とすればテーブルが表示されます。

+------+------+-------+
| id   | name | price |
+------+------+-------+
|    1 | LION |   200 |
|    2 | BAER |   180 |
+------+------+-------+
2 rows in set (0.00 sec)

次回コンテナ起動時にこのデータを見ることが出来ればよいわけですね。

MySQLのコンテナを再起動してデータを確認する

docker-compose downとして、コンテナが落ちることを確認します。

$ docker-compose down
Stopping docker_test_mysql-svc_1 ... done
Stopping docker_test_redis-svc_1 ... done
Removing docker_test_mysql-svc_1 ... done
Removing docker_test_redis-svc_1 ... done
Removing network docker_test_default

volumeが削除されていないという点をよくみておきましょう。そうしましたらもう一度立ち上げます。

$ docker-compose up -d
Creating network "docker_test_default" with the default driver
Creating docker_test_mysql-svc_1 ... done
Creating docker_test_redis-svc_1 ... done

前回と比べてvolumeが作成されていない点をよくみておきましょう。

$ docker-compose exec mysql-svc mysql -p
Enter password: 

mysql> use db;
Database changed

mysql> select * from hogehoge;
+------+------+-------+
| id   | name | price |
+------+------+-------+
|    1 | LION |   200 |
|    2 | BAER |   180 |
+------+------+-------+
2 rows in set (0.00 sec)

出ましたね。ちなみにRedisでも同じように確認することが出来ました。

お片付け

docker-compose downしてもvolumeが消えませんでした。実は次のコマンドを打つことでvolumeも一緒に削除することが出来ます。

docker-compose down -v
$ docker-compose down -v
Stopping docker_test_mysql-svc_1 ... done
Stopping docker_test_redis-svc_1 ... done
Removing docker_test_mysql-svc_1 ... done
Removing docker_test_redis-svc_1 ... done
Removing network docker_test_default
Removing volume docker_test_mysql_data
Removing volume docker_test_redis_data

【Python】configparserを使って.iniファイルから設定を呼び出そう

公式ドキュメントはこちら

docs.python.org

環境

python 3.6.6

iniファイルを書こう

config.ini

[load_type]
type = csv

[setting]
csv_file_path = test.csv

pythonの準備

import configparser

標準モジュールのconfigparserをimportする

config_file = configparser.ConfigParser()

インスタンスを作成する

config_file.read('./config.ini')

iniファイルはこのようにパスを指定してあげる。

iniファイルを読み込む

print(config_file.get('load_type', 'type'))

とすると

'csv'

が返ってきます。

これはconfig.ini

[load_type]
type = csv

を読み込んでいるからです。

なお、config_file['load_type']['type']として取得するケースも見受けられるけど、python3.7以上からみたいです。

iniファイルに環境変数を入れる

[load_type]
type = mysql
[setting]
host = %(MYSQL_IPADDR)s

環境変数を使うなら%(環境変数名)sとする必要がある。

%(環境変数名)s
"sまで必要"

python側ではconfigparserのインスタンス作成時にos.environを引数に入れる。

import configparser
import os
config_file = configparser.ConfigParser(os.environ)

そうして、以下のように呼ぶ

config_file.read('./config.ini')
print(config_file.get('setting', 'host'))

実行してみると

root@:/var/sandbox# export MYSQL_IPADDR=127.0.0.1
root@:/var/sandbox# echo $MYSQL_IPADDR
127.0.0.1
root@:/var/sandbox# python conf_test.py
127.0.0.1

【Python】【Flask-Migration】 flask db init でKeyError: 'migrate'って表示された。

こんばんは、葛の葉です。

flask-migrationを使っていて、コマンドでflask db initと実行したらKeyError: 'migrate'と表示されて実行されないということがありました。

File "/usr/local/lib/python3.6/site-packages/flask_migrate/__init__.py", line 125, in init
directory = current_app.extensions['migrate'].directory
KeyError: 'migrate'

環境

Python 3.6.6

Flask 1.0.2

Flask-Migrate 2.3.2

flask db initをする前に

flaskの起動方法について、認識をちょっと改める必要があって、そもそもflaskを起動するには以下のようにするのがお約束のようです。

flask run

python [flaskが記述されたpythonファイル]では正しい方法では無いようです。

flask.pocoo.org

それで、flaskは環境変数FLASK_APPに入っているpyファイルを読み込む設定のようで、環境変数FLASK_APPに中身が存在しない場合は、app.pyを読み込むようになっているようです。hello.pyにflaskのコードがある場合は以下のようにします。

export FLASK_APP=hello.py
flask run

flask-migrateのコマンドflask init dbとする場合もFLASK_APPが該当のものでないと駄目のようです。

stackoverflowで見てみる

stackoverflow.com

from flask_migrate import Migrate
migrate = Migrate(app, db) 

こうしなさいと書いてあった。

結局こうなった

# app.py

from flask_migrate import Migrate

from factory import create_app
from objects.database import db

app = create_app()
migrate = Migrate(app, db)

if __name__ == '__main__':
    app.run()

app.pyがおいてる場所をカレントディレクトリにして、flask db initが動いた。

上記のインポート先のコード

# factory.py

from flask import Flask
from objects.database import db

def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'hogehoge'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    db.init_app(app)
    return app
# objects/database.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def init_db(app):
    db.init_app(app)

【Oracle VirtualBox】ブリッジ設定を使ってホストマシンのローカルネットワークからゲストマシンにアクセスする

こんにちは、葛の葉です。

ホストマシンのローカルネットワーク環境からゲストマシンにアクセスする方法を記載します。

環境

Oracle VirtualBox 5.2.22
Host  Windows 10 Home
Guset Ubuntu 16.04

ブリッジ設定をする

VirtualBoxの画面でアクセスしたいゲストマシンを選択した状態で設定ボタンを押下します。左タブのネットワークを選択し、アダプター1の割り当てのプルダウンメニューをブリッジアダプターにして、名前のプルダウンメニューを現在のローカルネットワークに接続している"ホストマシンのネットワークアダプタ"にします。

f:id:Kuzunoha-NE:20190308121307j:plain

次にゲストマシンを起動してShellで以下のコマンドを打ちましょう。

ip address 

また、ip aでもいいです。自動補完してaaddressに変換されるっぽいので。(Aliasかも?)

そうすると、どこかのインターフェースでホストマシンと同じネットワークに繋がっているアドレスが存在しているのでメモってください。(この辺ざっくりしててごめんなさい🙇)これがこのゲストマシンにアクセスするためのIPアドレスになります。

f:id:Kuzunoha-NE:20190308123410j:plain

アクセスしてみる

例えばそのゲストマシンにNginxを導入していたら、192.168.1.?1にブラウザでアクセスするとWelcome to nginx!がちゃんと表示されているはず。

f:id:Kuzunoha-NE:20190308124637j:plain

同じネットワーク内ならスマホからでもアクセス可能です。