pyhaya’s diary

機械学習系の記事をメインで書きます

Kaggleのデータセットで遊んでみた 2 

前回の続きで、Titanicのデータセットで分析の基礎を学びます。

年齢と生存の関係を見てみる

前回の記事の最後に見たヒストグラムでは、Ageのデータがきれいな正規分布のような形をしているのが印象的でした。果たしてこの変数がSurviveに影響してくるのか、Survivedで分けてプロットしなおしてみます。

f,ax=plt.subplots(1,2,figsize=(20,10))
train[train['Survived']==0].Age.plot.hist(ax=ax[0],bins=20,edgecolor='black',color='red')
ax[0].set_title('Survived= 0')
x1=list(range(0,85,5))
ax[0].set_xticks(x1)
train[train['Survived']==1].Age.plot.hist(ax=ax[1],color='green',bins=20,edgecolor='black')
ax[1].set_title('Survived= 1')
x2=list(range(0,85,5))
ax[1].set_xticks(x2)
plt.show()

f:id:pyhaya:20181116000547p:plain
分布の形状に大きな差はありません。しかし、低年齢に注目してみると死者よりも明らかに生存者が多いことがわかります。

コードの説明

コード 動作
plt.subplots(1,2,figsize=(20,10)) プロット領域の用意。1行2列で、大きさは(20, 10)
train[train['Survived']==0].Age trainからSurvivedが0のものを抽出し、そのAgeカラムを取り出す
plot.hist(ax=ax[0], bins=20,...) 最初のプロット領域にヒストグラムをプロット。ビンは20個用意

性別と生存率の関係

性別と生存率の関係はどのようになっているでしょうか?

f,ax=plt.subplots(1,2,figsize=(18,8))
train[['Sex','Survived']].groupby(['Sex']).mean().plot.bar(ax=ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot('Sex',hue='Survived',data=train,ax=ax[1])
ax[1].set_title('Sex:Survived vs Dead')
plt.show()

f:id:pyhaya:20181116072422p:plain
今度は明確な差が出てきました。男性に比べると女性のほうが生存率が明らかに高いことがわかります。

コードの説明

コード 動作
.groupby(['Sex']).mean() 'Sex'でデータをまとめる。'Survived'の行の値は平均で置き換える
sns.countplot('Sex',hue='Survived',data=train,ax=ax[1]) trainデータで、横軸'Sex'縦軸'Survive'でプロット

複数の指標を同時に比較してみる

バイオリンプロット

複数の指標を一つのグラフ上にプロットするのはバイオリンプロットが便利です。sns.violinplotで利用できます。

f,ax=plt.subplots(1,2,figsize=(18,8))
sns.violinplot("Pclass","Age", hue="Survived", data=train,split=True,ax=ax[0],
              palette='pastel')
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0,110,10))
sns.violinplot("Sex","Age", hue="Survived", data=train,split=True,ax=ax[1],
              palette='pastel')
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))
plt.show()

f:id:pyhaya:20181116073730p:plain
ここから読み取れるのは、生存者数のピークは死者数のものとほぼ一致しているが、生存者数に関しては低年齢領域にもう一つ小さなピークがあるものが多いということです。また、Pclass=1では生存者数は1ピークですが、そのピーク位置は死者数と比較して低年齢側にシフトしています。

ペアプロット

sns.jointplot(x="Age", y="Survived", data=train, size=5,ratio=5, kind='kde', color='green')
plt.show()

f:id:pyhaya:20181116075802p:plain

ヒートマップ

ヒートマップで各変数の相関の強さをざっくりと見ます。

plt.figure(figsize=(7,4)) 
sns.heatmap(train.corr(),annot=True,cmap='Reds')
plt.show()

f:id:pyhaya:20181116080951p:plain
annot=Trueとすることで、相関係数がグラフ中に表示されるようになります。

Djangoで家計簿のWebアプリケーションを作る 7 ビューをクラスを使って整理する

Djangoで家計簿のWebアプリケーションを作っています。

ビューが汚い

ここまで様々な機能を実装してきました。その結果、views.pyの中身がだいぶ見づらくなってしまっています。

money/views.py

import calendar
import datetime
from django.shortcuts import render, redirect
from django.utils import timezone
import matplotlib.pyplot as plt
import pytz
from .models import Money
from .forms import SpendingForm
plt.rcParams['font.family'] = 'IPAPGothic'    #日本語の文字化け防止
# Create your views here.
TODAY = str(timezone.now()).split('-')

def index(request, year=TODAY[0], month=TODAY[1]):
    money = Money.objects.filter(use_date__year=year,                                                                                                                               use_date__month=month).order_by('use_date')

    total = index_utils.calc_month_pay(money)
    index_utils.format_date(money)
    form = SpendingForm()
    next_year, next_month = get_next(year, month)
    prev_year, prev_month = get_prev(year, month)

    context = {'year' : year,
            'month' : month,
            'prev_year' : prev_year,
            'prev_month' : prev_month,
            'next_year' : next_year,
            'next_month' : next_month,
            'money' : money,
            'total' : total,
            'form' : form
            }

    draw_graph(year, month)

    if request.method == 'POST':
        data = request.POST
        use_date = data['use_date']
        cost = data['cost']
        detail = data['detail']
        category = data['category']
        use_date = timezone.datetime.strptime(use_date, "%Y/%m/%d")
        tokyo_timezone = pytz.timezone('Asia/Tokyo')
        use_date = tokyo_timezone.localize(use_date)
        use_date += datetime.timedelta(hours=9)

        Money.objects.create(
                use_date = use_date,
                detail = detail,
                cost = int(cost),
                category = category,
                )
        return redirect(to='/money/{}/{}'.format(year, month))

    return render(request, 'money/index.html', context)

#...

どうにかしましょう。

リファクタリング

ビューの機能とは無関係の部分を抽出する

まずはビュー本来の機能である、コンテクストをHTMLに送るということ以外のことをしている部分を探し出して抽出していきましょう。まずはその月の支出合計を計算する部分は抽出できそうです。同様にして、データベースから日付をとってきて表示用に整形する部分も抽出できそうです。

money/utils/index_utils.py

def calc_month_pay(money):
    total = 0
    for m in money:
        total += m.cost

    return total

def format_date(money):
    for m in money:
        date = str(m.use_date).split(' ')[0]
        m.use_date = '/'.join(date.split('-')[1:3])

    return None

ついでに前月と次月を計算する関数もこちらに移してしまいましょう。

money/utils/index_utils.py

def calc_month_pay(money):
    total = 0
    for m in money:
        total += m.cost

    return total

def format_date(money):
    for m in money:
        date = str(m.use_date).split(' ')[0]
        m.use_date = '/'.join(date.split('-')[1:3])

    return None


def get_next(year, month):
    year = int(year)
    month = int(month)

    if month == 12:
        return str(year + 1), '1'
    else:
        return str(year), str(month + 1)

def get_prev(year, month):
    year = int(year)
    month = int(month)
    if month == 1:
        return str(year - 1), '12'
    else:
        return str(year), str(month - 1)

money/views.py

def index(request, year=TODAY[0], month=TODAY[1]):
    money = Money.objects.filter(use_date__year=year,                                                                                                                               use_date__month=month).order_by('use_date')

    total = index_utils.calc_month_pay(money)
    index_utils.format_date(money)
    form = SpendingForm()
    next_year, next_month = index_utils.get_next(year, month)
    prev_year, prev_month = index_utils.get_prev(year, month)

    context = {'year' : year,
            'month' : month,
            'prev_year' : prev_year,
            'prev_month' : prev_month,
            'next_year' : next_year,
            'next_month' : next_month,
            'money' : money,
            'total' : total,
            'form' : form
            }

    draw_graph(year, month)

    if request.method == 'POST':
        data = request.POST
        use_date = data['use_date']
        cost = data['cost']
        detail = data['detail']
        category = data['category']
        use_date = timezone.datetime.strptime(use_date, "%Y/%m/%d")
        tokyo_timezone = pytz.timezone('Asia/Tokyo')
        use_date = tokyo_timezone.localize(use_date)
        use_date += datetime.timedelta(hours=9)

        Money.objects.create(
                use_date = use_date,
                detail = detail,
                cost = int(cost),
                category = category,
                )
        return redirect(to='/money/{}/{}'.format(year, month))

    return render(request, 'money/index.html', context)

ビュークラスを使う

これで少しはすっきりしましたが、通常表示されるときに実行される部分と、postを受け取ったときに実行される部分が混ざってしまっています。これを解決するには、ビューをクラスとして書きます。

money/views.py

import calendar    
import datetime
from django.shortcuts import render, redirect
from django.utils import timezone
from django.views import View
import matplotlib.pyplot as plt
import pytz

from .models import Money
from .forms import SpendingForm
from .utils import index_utils

plt.rcParams['font.family'] = 'IPAPGothic'

TODAY = str(timezone.now()).split('-')

class MainView(View):
    def get(self, request, year=TODAY[0], month=TODAY[1]):
        money = Money.objects.filter(use_date__year=year,    
                            use_date__month=month).order_by('use_date')

        total = index_utils.calc_month_pay(money)
        index_utils.format_date(money)
        form = SpendingForm()
        next_year, next_month = index_utils.get_next(year, month)
        prev_year, prev_month = index_utils.get_prev(year, month)

        context = {'year' : year,
                'month' : month,
                'prev_year' : prev_year,
                'prev_month' : prev_month,
                'next_year' : next_year,
                'next_month' : next_month,
                'money' : money,
                'total' : total,
                'form' : form
                }

        draw_graph(year, month)

        return render(request, 'money/index.html', context)

    def post(self, request, year=TODAY[0], month=TODAY[1]):
        data = request.POST
        use_date = data['use_date']
        cost = data['cost']
        detail = data['detail']
        category = data['category']

        use_date = timezone.datetime.strptime(use_date, "%Y/%m/%d")
        tokyo_timezone = pytz.timezone('Asia/Tokyo')
        use_date = tokyo_timezone.localize(use_date)
        use_date += datetime.timedelta(hours=9)

        Money.objects.create(
                use_date = use_date,
                detail = detail,
                cost = int(cost),
                category = category,
                )
        return redirect(to='/money/{}/{}'.format(year, month)) 

#...

ここでは最も一般的なdjango.views.Viewをテンプレートビューとして使っています。この変更に伴って、urls.pyも少し変更する必要があります。

money/urls.py

from django.urls import path
from . import views

app_name = 'money'
urlpatterns = [
        path('', views.MainView.as_view(), name='index'),
        path('<int:year>/<int:month>', views.MainView.as_view(), name='index'),
        ]

このようにas_viewを付けることによってクラスをビューとして呼び出すことができます。

Kaggleのデータセットで遊んでみた 1 データの可視化

Kaggleとは、機械学習とデータサイエンスのプラットフォームのことです。このサイトでは、様々なデータを使って自分で分析を行うことができたり、データ解析のコンペティションに参加して精度を競い合ったりすることができます。

今回はKaggleの中で最初に出会うであろうTaitanicのデータセットを使ってデータ解析作業の大枠を紹介しているカーネルを紹介したいと思います。下のサイトを参考にしています。すごく良いカーネルなのでぜひ読んでみてください!
A Comprehensive ML Workflow with Python | Kaggle

作業環境

Kaggleのノートブック環境を利用しても、自分のJupyter Notebook環境を使っても構いません。構成の詳細は下のようになっています。

from sklearn.cross_validation import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
import matplotlib.pylab as pylab
import matplotlib.pyplot as plt
from pandas import get_dummies
import matplotlib as mpl
import seaborn as sns
import pandas as pd
import numpy as np
import matplotlib
import warnings
import sklearn
import scipy
import numpy
import json
import sys
import csv
import os

print('matplotlib: {}'.format(matplotlib.__version__))
print('sklearn: {}'.format(sklearn.__version__))
print('scipy: {}'.format(scipy.__version__))
print('seaborn: {}'.format(sns.__version__))
print('pandas: {}'.format(pd.__version__))
print('numpy: {}'.format(np.__version__))
print('Python: {}'.format(sys.version))

# 以下出力
"""
/opt/conda/lib/python3.6/site-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
matplotlib: 2.2.3
sklearn: 0.19.1
scipy: 1.1.0
seaborn: 0.9.0
pandas: 0.23.4
numpy: 1.15.2
Python: 3.6.6 |Anaconda, Inc.| (default, Oct  9 2018, 12:34:16) 
[GCC 7.3.0]
"""

基本設定

この後、様々な処理をしていくにあたって、プロットについて基本的な設定をしておきます。

sns.set(style='white', context='notebook', palette='deep')
pylab.rcParams['figure.figsize'] = 12,8
warnings.filterwarnings('ignore')
mpl.style.use('ggplot')
sns.set_style('white')
%matplotlib inline

ここら辺のスタイルは各自の好みで変えてよいと思います。

データを見てみる

散布図

train = pd.read_csv('../input/train.csv')
test = pd.read_csv('../input/test.csv')

train_columns
#以下出力
"""
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')
"""

このデータには「PassengerId」など12個のカラムがあります。今回予測するのは'Survived'つまりタイタニック事故で特定の人が生き残るか死ぬかを予測することになります。そのため、データの分析は基本的には'Survived'とほかの変数の間の関係を見ていくことになります。

試しに、Pclass(客の経済的な豊かさ、1が最も裕福)、年齢、乗船料とSurviveの関係を見てみます。

g = sns.FacetGrid(train, hue="Survived", col="Pclass", margin_titles=True,
                  palette={1:"seagreen", 0:"gray"})
g=g.map(plt.scatter, "Fare", "Age",edgecolor="w").add_legend();

下のようなグラフが表示されます。
f:id:pyhaya:20181115231859p:plain
生存者と死者の間に明確な違いは見受けられません。

コードの説明

1行目のsns.FaceGridではtrainからデータをとってきてプロットをすることを示し、colに指定されたPclassごとにグラフを用意することを宣言しています。なので今回はPclassが1,2,3の3つのグラフが描画されます。hueはSurviveの値で区別してプロットすることを意味しています。Surviveには生存(1)と死亡(0)の2種類がありますが、それぞれを何色でプロットするかはpaletteで指定されています。

実際のグラフ描画はg.mapで行われています。

コード 動作
plt.scatter 散布図スタイルでプロットする
"Fare" 横軸はFare(乗船料)
"Age" 縦軸はAge(年齢)
edgecolor ドットの淵は白(white)
add.legend() 凡例を入れる

Boxプロット(箱ひげ図)

各変数がどのような値の範囲をとるのか図示します。

train.plot(kind='box', subplots=True, layout=(2,4), sharex=False, sharey=False)
plt.subplots_adjust(wspace=0.5, hspace=0.6)

f:id:pyhaya:20181115233910p:plain

箱ひげ図の見方についてはWidipediaを見てください。

コードの説明
コード 動作
train.plot Pandasライブラリの機能でプロットする
kind='box' プロットの種類は箱ひげ図
subplots=True グラフをサブプロットに分ける
layout=(2, 4) 2行4列にサブプロットを配置する
sharex=False, sharey=False x, y軸の値の範囲を共有しない
plt.subplots_adjust(wspace=0.5, hspace=0.6) グラフ同士の幅、高さの間隔調整

ヒストグラム

ヒストグラムは、各変数値にどのくらいのデータがあるのか知るのに便利です。

train.hist(figsize=(15, 20))
plt.show()

f:id:pyhaya:20181115235614p:plainf:id:pyhaya:20181115235618p:plainf:id:pyhaya:20181115235611p:plain

「テスト駆動開発」をPythonで書き直してみた 7

書籍「テスト駆動開発」をPythonで書き直したシリーズです。前回の記事はこちらです。
pyhaya.hatenablog.com

今回は、いよいよ多国通貨を扱うための準備に取り掛かります。

テスト駆動開発

テスト駆動開発

Bank(銀行)

多国通貨を扱うためには、為替レートを使って通貨を換算する必要があります。その役割を銀行クラスを作って任せます。どのような動作を期待するのか、テストを書いて明示します。

tests/test_money.py

import sys
sys.path.append('../src')

import unittest
from money import Money

class MoneyTest(unittest.TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five * 2)
        self.assertEqual(Money.dollar(15), five * 3)

    def testEquality(self):
        self.assertNotEqual(Money.franc(5), Money.dollar(5))

    def testSimpleAddition(self):
        bank = Bank()    #銀行を用意
        sum_ = Money.dollar(5) + Money.dollar(5)
        reduced = bank.reduce(sum_, "USD")    #USDに換算する
        self.assertEqual(reduced, Money.dollar(10))

if __name__ == '__main__':
    unittest.main()

銀行を表すオブジェクトを用意しておいて、reduceメソッドで通貨の両替を行います。

明白な実装

このテストを通す明白な実装はこのようになります。

src/bank.py

from money import Money

class Bank:
    def reduce(self, source, to):
        return Money.dollar(10)

Moneyの実装を再考する

ここまで書いたら、一度Moneyの実装を考えてみます。今回、両替を銀行に委譲しました。なので、通貨同士の足し算は足した時点では通貨は決定していない状態です。ただ、足される2つの通貨を持っているだけの中間状態のようなものが必要です。いわば抽象的な通貨を表すためのクラスが必要です。

Sumクラスを実装する

このようなクラスとして、Sumクラスを作ってみます。

src/sum.py

class Sum:
    def __init__(self, augent, addend):
        self.augent = augent
        self.addend = addend

src/money.py

from sum import Sum

class Money:
    def __init__(self, amount, currency):
        self.amount = amount
        self.currency = currency

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __add__(self, other):
        return Sum(self, other)

    def __mul__(self, multiplier):
        return Money(self.amount * multiplier, self.currency)
    
    @staticmethod
    def dollar(amount):
        return Money(amount, 'USD')

    @staticmethod
    def franc(amount):
        return Money(amount, 'CHF')

__add__メソッドはSumクラスのインスタンスを返します。Sumクラスは初期化の際に被加算数(addend)と加算数(augent)を持ちます。

Bankクラスの本実装

ここまでくれば、ロジックが固まってきたのでBankクラスのreduceメソッドを書き下せます。source引数はSumクラスのインスタンスを引数に持つので、

src/bank.py

from money import Money

class Bank:
    def reduce(self, source, to):
        amount = source.augent.amount + source.addend.amount
        return Money(amount, to)

まとめ

今回は多国通貨を扱うための下準備を行いました。ここまでくるとだいぶロジックが複雑になってくるのでテストを書いてどのようなロジックにしたいのか明確にしておくことが重要です。

AtCoder Beginners Contest (ABC) 011 C: 123引き算 を解いた

競技プログラミング初心者が初心者向けに問題の解説を行います。

使用環境

問題文

今回挑戦するのは、次の問題です。

あなたは、友人から、一人用のゲームを紹介されました。

最初に、数字 N が与えられます。1, 2, 3 の中から好きな数字を選び、 与えられた数字に対し、引き算を行う、という処理を行うことできます。この処理は100回まで行うことが可能であり、最終的に数字を0にすることが目標のゲームです。しかし、計算途中でなってはいけないNG数字が3つ与えられており、 この数字に一時的にでもなってしまった瞬間、このゲームは失敗となります。 NG数字がN と同じ場合も失敗となります。

あなたは、このゲームが、目標達成可能なゲームとなっているか調べたいです。

目標達成可能な場合はYES、そうでない場合はNOと出力してください。

入力は以下の形式で標準入力から与えられる。

N
NG1
NG2
NG3

1行目には、最初に与えられる数字N(1≦N≦300)が与えられる。
2行目には、1番目のNG数字NG1(1≦NG1≦300)が与えられる。
3行目には、2番目のNG数字NG2(1≦NG2≦300)が与えられる。
4行目には、3番目のNG数字NG3(1≦NG3≦300) が与えられる。

例えば N=2NG1=1, NG2=7, NG3=15の時には1を引くとNG1に一致してしまうのでダメで、2を引けば無事0になるので出力は"YES"となります。

単純に考える

単純に考えると、この問題は再帰関数を使って全探索で解くことができる気がします。

#include <iostream>
using namespace std;

int ng1, ng2, ng3;
bool solve(int res, int k) {    // res : 残り    k : 引き算を行った回数
        // 残りが0になっていればOK
	if (res == 0) {
		return true;
	}
        // 0を行き過ぎていたり、NGの数に一致してしまったらダメ
	else if (res < 0 || res == ng1 || res == ng2 || res == ng3) {
		return false;
	}
        // 引き算を100回以上したらダメ
	else if (k >= 100) {
		return false;
	}
        // それ以外だったら1引いた場合と2引いた場合と3引いた場合を計算してみて
        // どれか一つでも成功したらOK
	else {
		return solve(res - 1, k + 1) || solve(res - 2, k + 1) || solve(res - 3, k + 1);
	}

}

int main() {
	int n; cin >> n;
	cin >> ng1 >> ng2 >> ng3;

	if (solve(n, 0)) cout << "YES" << endl;
	else cout << "NO" << endl;
}

しかし、これはNが大きくなると時間的に全然間に合わなくなります。なぜなら、

return solve(res - 1, k + 1) || solve(res - 2, k + 1) || solve(res - 3, k + 1);

を見ると、1つの計算のために3種類の計算をすることになっているので、再帰が深くなると指数関数的に計算量が増えていくからです。今回、Nは300まで行きますから、単純計算で、最大の計算量は3^{300}にもなります。

満点解法

制限時間内にプログラムが愁傷するためには、視点を変える必要があります。ある数mに到達するまでの引き算の必要最低限の回数を計算していって、0になるまでに必要な回数が100回以下であればよいと考えます。

mに到達するまでの引き算の回数の最小値をdp[m]とします。配列dpには最初には十分大きな数を入れておきます。そしてNは初期値なのでdp[N]=0をセットします。後はNから順番に小さいほうに進んでいき、配列の要素を更新していきます。更新の様子を図示すると下のようになります。
f:id:pyhaya:20181113000910p:plain

これをコードに落とし込むと、

#include <iostream>
#include <algorithm>
using namespace std;

int INF = 1000000;
int ng1, ng2, ng3;
int dp[305];

int main() {
	int n; cin >> n;
	cin >> ng1 >> ng2 >> ng3;

	for (int i = 0; i < n; i++) {
		dp[i] = INF;
	}
	dp[n] = 0;

	for (int i = n; i != 0; i--) {
		if (i == ng1 || i == ng2 || i == ng3) {
			continue;
		}
		for (int j = 1; j < 4; j++) {
			dp[i - j] = min(dp[i - j], dp[i] + 1);
		}
	}

	if (dp[0] <= 100) cout << "YES" << endl;
	else cout << "NO" << endl;
}

これで時間内になります。

「データサイエンティスト ハッチングフェス by アビームコンサルティング・神戸製鋼所」に参加した

ハッチングフェスに参加

techplay.jp

今日はDataShipさんが主宰するデータサイエンス系のハッチングフェスに参加してきました。ここでは、企業でのデータサイエンスの活用状況のほか、アイデアソンを行いました。

このイベントの参加企業はタイトルにもありますが、以下の2社です。

www.abeam.com

KOBELCO 神戸製鋼

企業の話を聞いての感想

アビームコンサルティング

アビームは言わずと知れた日本発のコンサルティング会社です。コンサルティングファームに対しては漠然とした理解しかなかったのですが、コンサルには会社によって戦略立案に特化していたり、システム開発に特化していたりするが、アビームではこのすべての案件を手掛けているというのが印象的でした。

また、比較的短いスパンで様々な企業のコンサルティングを手掛けるので、様々なデータに触れられるほか、広い分野の知識が得られるのかなという印象を受けました。

神戸製鋼所

神戸製鋼所は鉄鋼会社です。同業には新日鉄住金などがあると思いますが、同業他社とは違って、鉄鋼製品以外にも大きなシェアを持っているというのが驚きでした。自社で発電所を持っていたり、都市交通システムの設計を行っているというのが印象的でした。

神戸製鋼所で扱っているデータは、製鉄過程の外側からは見えない部分の制御に使うそうです。

イデアソン

私は今回、アビームコンサルティングのアイデアソンに参加しました。このアイデアソンでは、顧客からの依頼を解決するための手順を実際に体験してみました。データ分析といってもKaggleでするようなゴリゴリとコードを書いてモデルを作ったり最適解を求めたりするようなものではなく、非常に簡略化された状況の中で、どのように問題点を洗い出して、それに対する解決策を提案していくかという、データサイエンスの考え方のようなものを学ぶ感じでした。

しかし、普段コンサルタントの仕事内容というのは間近で見ることも、ましてや自ら経験することもないと思うので、こんな風に問題を解決するんだなというざっくりとした感覚がわかっただけでも大変貴重であったように感じます。

懇親会

イデアソンが終わった後は懇親会がありました。ここではラフな感じで社員の方々が普段どのように過ごしているのか、や業務内容の詳しい話を伺うことができました。



今回は、普段のプログラミングの記事とは異なり、自分が参加した技術系イベントがどんな感じだったか書きました。企業がデータを提供してくれるイベントだったので、アイデアソンの詳しい内容などは書けず、漠然とした感じになってしまったかもしれません。データサイエンスに興味があるけど実際どのような仕事なのかわからないとか、普段何を考えて仕事をしているのか知りたいという人にこんなイベントがあるんだよというのを知ってもらえたらうれしいです。

Djangoで家計簿のWebアプリケーションを作る 6 HTMLを整理する

Djangoで家計簿のWebアプリケーションを作る記事の第6弾です。今回は、実際に表示されるHTMLの部分を整理していきます。
前回の記事はこちら
pyhaya.hatenablog.com

前後の月へのリンクを張る

money/templates/money/index.html

<!DOCTYPE html>
{% load static %}
<html>
    <head>
        <meta charset="utf-8">
        <title>HousekeepingBook</title>
        <link rel="stylesheet" type="text/css" 
        href="{% static 'money/style.css' %}">
    </head>
    <body>
        <div class="top">
            <div "year-month">
                <h1>{{ year }}年{{ month }}月</h1>
            </div>
            <div class="move_month">
                //ここ
                <a href="/money/{{ prev_year }}/{{ prev_month }}">{{ prev_month }}月</a>
            </div>
            <div class="move_month">
                //ここ
                <a href="/money/{{ next_year }}/{{ next_month }}">{{ next_month }}月</a>
            </div>
        </div>
        <div class="outer">
            <div class="form-money">
                <form action="/money/" method="post">
                    {% csrf_token %}
                    {{ form.as_table }}
                    <input type="submit" value="送信">
                </form>
            </div>
            <div class="wrapper">
                <div class="main">
                    <table>
                        <tr>
                            <th>日付</th>
                            <th>用途</th>
                            <th>カテゴリー</th>
                            <th>金額</th>
                        </tr>
                        {% for m in money %}
                        <tr>
                            <td>{{ m.use_date }}</td>
                            <td>{{ m.detail }}</td>
                            <td>{{ m.category }}</td>
                            <td>{{ m.cost }}円</td>
                        </tr>
                        {% endfor %}
                    </table>
                    <div class="tot">
                        合計:{{ total }}円
                    </div>
                </div>
                <div class="main">
                    <img src="/static/images/bar_{{ year }}_{{ month }}.svg"
                    width=80%>
                </div>
            </div>
        </div>
    </body>
</html>

これに合わせてビューに新たなコンテクストprev_month等を追加します。

money/views.py

#...
def index(request, TODAY[0], TODAY[1]):
    #...
    next_year, next_month = index_utils.get_next(year, month)
    prev_year, prev_month = index_utils.get_prev(year, month)

    context = {'year' : year, 
            'month' : month,
            'prev_year' : prev_year,
            'prev_month' : prev_month,
            'next_year' : next_year,
            'next_month' : next_month,
            'money' : money,
             'total' : total,
             'form' : form
            }
#...

def get_next(year, month):
    year = int(year)
    month = int(month)
    
    if month == 12:
        return str(year + 1), '1'
    else:
        return str(year), str(month + 1)

def get_prev(year, month):
    year = int(year)
    month = int(month)
    if month == 1:
        return str(year - 1), '12'
    else:
        return str(year), str(month - 1)

スタイルを整える

ここまでページのスタイルはあまり気にせずやってきましたがここで少し整理します。

money/static/money/style.css

table{
  border-collapse:collapse;
  margin:0 0;
}
th{
  color:#005ab3;
}
td{
  border-bottom:1px dashed #999;
}
th,tr:last-child td{
  border-bottom:2px solid #005ab3;
}
td,th{
  padding:10px;
}

/* upper side of the page */
.top div{
    float: left;
}

.top{
    background-color: #8eff8e;
    overflow: hidden;
}

.year-month{
    margin: 20px 40px 0px 20px;
}

.move_month{
    margin: 30px 10px 10px 10px;
    font-size: 18px;
}

/* style around form */
.form-money{
    margin-top: 20px;
}

/* main part*/
.tot {
    font-weight: 900;
}

.outer {
    clear: both;
}

.main{
    float: left;
    padding: 10px;
    margin: 20px;
}

.wrapper {
    display: flex;
}

#canvas {
    width: 100%;
    height: 100%;
}

結果

ここまでやると、ページは下のようになります。
f:id:pyhaya:20181115074228p:plain

少しはましになりました。