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()
分布の形状に大きな差はありません。しかし、低年齢に注目してみると死者よりも明らかに生存者が多いことがわかります。
コードの説明
コード | 動作 |
---|---|
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()
今度は明確な差が出てきました。男性に比べると女性のほうが生存率が明らかに高いことがわかります。
コードの説明
コード | 動作 |
---|---|
.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()
ここから読み取れるのは、生存者数のピークは死者数のものとほぼ一致しているが、生存者数に関しては低年齢領域にもう一つ小さなピークがあるものが多いということです。また、Pclass=1では生存者数は1ピークですが、そのピーク位置は死者数と比較して低年齢側にシフトしています。
ペアプロット
sns.jointplot(x="Age", y="Survived", data=train, size=5,ratio=5, kind='kde', color='green') plt.show()
ヒートマップ
ヒートマップで各変数の相関の強さをざっくりと見ます。
plt.figure(figsize=(7,4)) sns.heatmap(train.corr(),annot=True,cmap='Reds') plt.show()
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();
下のようなグラフが表示されます。
生存者と死者の間に明確な違いは見受けられません。
コードの説明
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)
箱ひげ図の見方については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) |
グラフ同士の幅、高さの間隔調整 |
「テスト駆動開発」をPythonで書き直してみた 7
書籍「テスト駆動開発」をPythonで書き直したシリーズです。前回の記事はこちらです。
pyhaya.hatenablog.com
今回は、いよいよ多国通貨を扱うための準備に取り掛かります。
- 作者: Kent Beck,和田卓人
- 出版社/メーカー: オーム社
- 発売日: 2017/10/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
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引き算 を解いた
競技プログラミング初心者が初心者向けに問題の解説を行います。
使用環境
- Windows10
- Visual Studio 2017
問題文
今回挑戦するのは、次の問題です。
あなたは、友人から、一人用のゲームを紹介されました。
最初に、数字 N が与えられます。1, 2, 3 の中から好きな数字を選び、 与えられた数字に対し、引き算を行う、という処理を行うことできます。この処理は100回まで行うことが可能であり、最終的に数字を0にすることが目標のゲームです。しかし、計算途中でなってはいけないNG数字が3つ与えられており、 この数字に一時的にでもなってしまった瞬間、このゲームは失敗となります。 NG数字がN と同じ場合も失敗となります。
あなたは、このゲームが、目標達成可能なゲームとなっているか調べたいです。
目標達成可能な場合はYES、そうでない場合はNOと出力してください。
入力は以下の形式で標準入力から与えられる。
N NG1 NG2 NG31行目には、最初に与えられる数字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) が与えられる。
例
例えばでの時には1を引くとに一致してしまうのでダメで、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; }
しかし、これはが大きくなると時間的に全然間に合わなくなります。なぜなら、
return solve(res - 1, k + 1) || solve(res - 2, k + 1) || solve(res - 3, k + 1);
を見ると、1つの計算のために3種類の計算をすることになっているので、再帰が深くなると指数関数的に計算量が増えていくからです。今回、は300まで行きますから、単純計算で、最大の計算量はにもなります。
満点解法
制限時間内にプログラムが愁傷するためには、視点を変える必要があります。ある数に到達するまでの引き算の必要最低限の回数を計算していって、0になるまでに必要な回数が100回以下であればよいと考えます。
に到達するまでの引き算の回数の最小値をdp[m]
とします。配列dp
には最初には十分大きな数を入れておきます。そしては初期値なのでdp[N]=0
をセットします。後はNから順番に小さいほうに進んでいき、配列の要素を更新していきます。更新の様子を図示すると下のようになります。
これをコードに落とし込むと、
#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 アビームコンサルティング・神戸製鋼所」に参加した
ハッチングフェスに参加
今日はDataShipさんが主宰するデータサイエンス系のハッチングフェスに参加してきました。ここでは、企業でのデータサイエンスの活用状況のほか、アイデアソンを行いました。
このイベントの参加企業はタイトルにもありますが、以下の2社です。
企業の話を聞いての感想
アビームコンサルティング
アビームは言わずと知れた日本発のコンサルティング会社です。コンサルティングファームに対しては漠然とした理解しかなかったのですが、コンサルには会社によって戦略立案に特化していたり、システム開発に特化していたりするが、アビームではこのすべての案件を手掛けているというのが印象的でした。
また、比較的短いスパンで様々な企業のコンサルティングを手掛けるので、様々なデータに触れられるほか、広い分野の知識が得られるのかなという印象を受けました。
アイデアソン
私は今回、アビームコンサルティングのアイデアソンに参加しました。このアイデアソンでは、顧客からの依頼を解決するための手順を実際に体験してみました。データ分析といっても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%; }
結果
ここまでやると、ページは下のようになります。
少しはましになりました。