Numpy以外のコンテナ
Python では,値の集合を「コンテナ」と呼び,代表的なものとして
- リスト(List):
[1, 'two', [3], 3.14]
- タプル(Tuple):
(1, 'two', [3], 3.14)
- 文字列(Str):
'aa83988848'
- 集合(Set):
{1, 'two', [3], 3.14}
- 辞書(Dict):
{'v1':1, 'v2':'two', 'v3':[3], 'v4':3.14}
があります.コンテナは,基本的に各要素が異なる型(クラス)でも構いません.要素がさらにコンテナであっても構いません.
MyList = [1, 'two', [3], 3.14]
MyTuple = (1, 'two', [3], 3.14)
MyStr = 'aa83988848'
MySet = {1, 'two', 3.14} # setはコンテナを要素に持てない
MyDict = {'v1':1, 'v2':'two', 'v3':[3], 'v4':3.14}
print('==MyList==')
print(f'{MyList}\n')
print('==MyTuple==')
print(f'{MyTuple}\n')
print('==MyStr==')
print(f'{MyStr}\n')
print('==MySet==')
print(f'{MySet}\n')
print('==MyDict==')
print(f'{MyDict}\n')
==MyList==
[1, 'two', [3], 3.14]
==MyTuple==
(1, 'two', [3], 3.14)
==MyStr==
aa83988848
==MySet==
{1, 3.14, 'two'}
==MyDict==
{'v1': 1, 'v2': 'two', 'v3': [3], 'v4': 3.14}
Numpy以外のコンテナに対する演算
これらのコンテナに対して演算をすると
print("==MyList * 3==")
print(f"{MyList * 3}\n")
print("==MyTuple * 3==")
print(f"{MyTuple * 3}\n")
print("==MyStr * 3==")
print(f"{MyStr * 3}\n")
# print('==MySet * 3==')
# print(f'{MySet * 3}\n') # Setは演算できない
# print('==MyDict * 3==')
# print(f'{MyDict * 3}'\n) # Dictは演算できない
MyList2 = [1, 2, 3]
print("==MyList + MyList2==")
print(f"{MyList + MyList2}\n")
==MyList * 3==
[1, 'two', [3], 3.14, 1, 'two', [3], 3.14, 1, 'two', [3], 3.14]
==MyTuple * 3==
(1, 'two', [3], 3.14, 1, 'two', [3], 3.14, 1, 'two', [3], 3.14)
==MyStr * 3==
aa83988848aa83988848aa83988848
==MyList + MyList2==
[1, 'two', [3], 3.14, 1, 2, 3]
というように,後ろに繰り返し要素を付け加えることになります.
numpy.NDarray
はベクトル演算
それぞれの要素に対し演算するには,どうしたらいいでしょうか.そのような演算をベクトル演算といいます.
ベクトル演算をするためのコンテナを Numpy NDArray
といいます.
NumPy というパッケージに NumPy NDArray クラスが定義されています.
import numpy as np
MyList = [2, 3, 5,7]
print('==MyList==')
print(f'{MyList}\n')
print('==MyList * 3==')
print(f'{MyList * 3}\n')
MyArray = np.array(MyList) # <-- Numpy NDArray
print('==MyArray==')
print(f'{MyArray}\n')
print('==MyArray * 3==')
print(f'{MyArray * 3}\n')
==MyList==
[2, 3, 5, 7]
==MyList * 3==
[2, 3, 5, 7, 2, 3, 5, 7, 2, 3, 5, 7]
==MyArray==
[2 3 5 7]
==MyArray * 3==
[ 6 9 15 21]
要素数の異なる NumpyArray との加算はできません.
MyArray2 = np.array(MyList2)
print(f"{MyArray + MyArray2}")
ValueError: operands could not be broadcast together with shapes (4,) (3,)
NumpyArray は,線形代数における行列やベクトルなので,行列やベクトルに対する演算しか許されないというわけです.
numpy.NDArray
は本当はテンソル演算
「NumpyArray は,行列やベクトル」と言いましたが,行列やベクトルには形があります.
M = [1 2 3]
[4 5 6]
のとき,M の形は「2 行 3 列」です.
v = [7]
[8]
[9]
のとき,v の形は「3 行 1 列」ですね.
u = [10 11]
のとき,u の形は「1 行 2 列」ですね.
NumpyArray で 2 行 3 列の行列を作ってみましょう. すべての要素がゼロな 2 行 3 列の行列は,Numpy の zeros 関数で作ることができます.
M = np.zeros((2,3))
print(M)
行列の形は 属性.shape
に保存されています.
形(2,3)の M をprint
したときの表示を見てみると,「行ごとの 3 つの要素がリストになっており,2 行のリストをさらにリストに」しています.
実は,このようなリストから NumpyArray を作ることもできます.
u1 = [1,2,3]
u2 = [4,5,6]
L = [u1, u2]
M = np.array(L)
print(M)
print(M.shape)
では,7 と 8 と 9 を要素にもつ 1 行 3 列の NumpyArray を作ってみましょう.行ごとにリストに,全体をさらにリストにします.つまり 2 重リストになります.
Lx = [[7,8,9]]
x = np.array(Lx)
print(x)
print(x.shape)
この x を転置すれば,Mx が計算できます.転置は Numpy のtranspose
関数を使います.また,行列積は Numpy のmatmul
関数です.(ちなみに*
は要素積(要素同士の積)です.)
R = np.matmul(M, np.transpose(x))
print(R)
print(R.shape)
皆が知っているように,行列積は,「左の行列の列数」と「右の行列の行数」が同じでなければいけません.上の例の場合,(2,3)と(3,1)なので行列積は(2,1)となるわけですね.
ところで,x は行列でしょうか?ベクトルでしょうか?
行ベクトルとか列ベクトルと言われているものですが,Numpy では 「行数」とか「列数」とか言っているので,これは「行列」なのです.では,ベクトルは?
ベクトルとは,1 重のリストから作ったものをいいます.「行」や「列」という概念がありません.
Ly = [7,8,9]
y = np.array(Ly)
print(y)
print(y.shape)
さきほどの x は形が(1,3)で,転置したnp.transpose(x)
は(3,1)でした.今度の y は(3,)です.さきほどの行列積ができます.
R2 = np.matmul(M, y)
print(R2)
print(R2.shape)
R2 はさきほどの R と要素は同じ数値ですが,(2,1)ではなく(2,)となります.(行列積の作られ方どおりです.)
まとめると
行列: 2 重のリストから作られる.形は (S1, S2),行数 S1, 列数 S2
ベクトル: 1 重のリストから作られる.形は (S1,), 要素数 S1
行列積は,np.matmul
関数で,
- 左の Array の形が(S1,S2)で,右の Array の形が(S3,S4)のとき,S2==S3 なら演算できて,答えの Array の形は(S1,S4)
転置は np.transpose
関数で,
- 形が(S1,S2)の行列の転置は,形が(S2,S1)となる
- 形が(S1,)のベクトルの転置は,形が変わらず(S1,)となる
print(y.shape)
yt = np.transpose(y)
print(yt.shape)
テンソル
行列やベクトルをまとめて,一般的に「テンソル」と言います.
さきほどの「n 重のリスト」のn
を階数といいます.
- 行列: 2 階テンソル
- ベクトル: 1 階テンソル
ここで,1 重リストを並べたものをさらにリストにすれば,2 重リストになるので,すなわち(S1,)ベクトル(1 階テンソル)を S2 個並べると(S2,S1)行列(2 階テンソル)になるということです. 要素が(S1,)ベクトルである(S2,)ベクトルは,(S2,S1)という行列になります.
属性.ndim
に保存されています.属性.shape
に保存されている「形」を表すタプルの要素数でもあります.
A1 = np.array([1,2,3,4])
print(A1)
print(f'shape(形):{A1.shape}')
print(f'ndim(階数):{A1.ndim}')
print(f'size(要素数):{A1.size}')
print('\n')
A2 = np.array([[1,2,3,4]])
print(A2)
print(f'shape(形):{A2.shape}')
print(f'ndim(階数):{A2.ndim}')
print(f'size(要素数):{A2.size}')
print('\n')
A3 = np.array([[[1,2,3,4]]])
print(A2)
print(f'shape(形):{A3.shape}')
print(f'ndim(階数):{A3.ndim}')
print(f'size(要素数):{A3.size}')
print('\n')
[1 2 3 4]
shape(形):(4,)
ndim(階数):1
size(要素数):4
[[1 2 3 4]]
shape(形):(1, 4)
ndim(階数):2
size(要素数):4
[[1 2 3 4]]
shape(形):(1, 1, 4)
ndim(階数):3
size(要素数):4
3 階以上になっても行列積(テンソル積)は同じです.
T3 = np.array([[[1,2,3,4]]])
T1 = np.array([5,6,7,8])
Result = np.matmul(T3,T1)
print(f'{T3.ndim}階テンソル(形{T3.shape})と{T1.ndim}階テンソル(形{T1.shape})の行列積は{Result.ndim}階テンソル(形{Result.shape})')
print(Result)
3階テンソル(形(1, 1, 4))と1階テンソル(形(4,))の行列積は2階テンソル(形(1, 1))
[[70]]
抽出
M = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(M,type(M),M.shape,M.ndim,M.size,'\n')
M_row = M[1] # 行
print(M_row,type(M_row),M_row.shape,M_row.ndim,M_row.size,'\n')
M_item = M[1][2] # (1,2)要素 M[1,2]とは書けない
print(M_item,type(M_item),M_item.shape,M_item.ndim,M_item.size,'\n')
M_slice = M[1:3,0:2] #スライス
print(M_slice,type(M_slice),M_slice.shape,M_slice.ndim,M_slice.size,'\n')
M_column = M[:,1] # 列抽出はスライスで
print(M_column,type(M_column),M_column.shape,M_column.ndim,M_column.size,'\n')
M_sub = M[[0,2],[1,3]] # (0,1)要素と(2,3)要素 (スライス)
print(M_sub,type(M_sub),M_sub.shape,M_sub.ndim,M_sub.size)
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]] <class 'numpy.ndarray'> (3, 4) 2 12
[5 6 7 8] <class 'numpy.ndarray'> (4,) 1 4
7 <class 'numpy.int64'> () 0 1
[[ 5 6]
[ 9 10]] <class 'numpy.ndarray'> (2, 2) 2 4
[ 2 6 10] <class 'numpy.ndarray'> (3,) 1 3
[ 2 12] <class 'numpy.ndarray'> (2,) 1 2
追加
u1 = np.array([[1,2],[3,4]])
u2 = np.array([[11,12],[13,14]])
print('==np.concat: 階数を変えずに,連結する.==')
M1 = np.concatenate([u1,u2])
print(M1)
print(f'形:{M1.shape},階:{M1.ndim},要素数:{M1.size}\n')
M2 = np.concatenate([u1,u2],axis=1)
print(M2)
print(f'形:{M2.shape},階:{M2.ndim},要素数:{M2.size}\n')
print('==np.stack: 指定した階に,積む==')
M3 = np.stack([u1,u2],axis=0)
print(M3)
print(f'形:{M3.shape},階:{M3.ndim},要素数:{M3.size}\n')
M4 = np.stack([u1,u2],axis=1)
print(M4)
print(f'形:{M4.shape},階:{M4.ndim},要素数:{M4.size}\n')
M5 = np.stack([u1,u2],axis=2)
print(M5)
print(f'形:{M5.shape},階:{M5.ndim},要素数:{M5.size}\n')
==np.concat: 階数を変えずに,連結する.==
[[ 1 2]
[ 3 4]
[11 12]
[13 14]]
形:(4, 2),階:2,要素数:8
[[ 1 2 11 12]
[ 3 4 13 14]]
形:(2, 4),階:2,要素数:8
==np.stack: 指定した階に,積む==
[[[ 1 2]
[ 3 4]]
[[11 12]
[13 14]]]
形:(2, 2, 2),階:3,要素数:8
[[[ 1 2]
[11 12]]
[[ 3 4]
[13 14]]]
形:(2, 2, 2),階:3,要素数:8
[[[ 1 11]
[ 2 12]]
[[ 3 13]
[ 4 14]]]
形:(2, 2, 2),階:3,要素数:8
形を変形
T = np.array([[1,2,3,4],[5,6,7,8]])
print(T)
print(f'形:{T.shape}, 階:{T.ndim}, 要素数:{T.size}\n')
T2 = np.reshape(T,(1,8))
print(T2)
print('前から順に8要素のリストを1個')
print(f'形:{T2.shape}, 階:{T2.ndim}, 要素数:{T2.size}\n')
T3 = np.reshape(T,(8,1))
print(T3)
print('前から順に1要素のリストを8個')
print(f'形:{T3.shape}, 階:{T3.ndim}, 要素数:{T3.size}\n')
T4 = np.reshape(T,(4,2))
print(T4)
print('前から順に2要素のリストを4個')
print(f'形:{T4.shape}, 階:{T4.ndim}, 要素数:{T4.size}\n')
T5 = np.reshape(T,(8,))
print(T5)
print('前から順に8要素')
print(f'形:{T5.shape}, 階:{T5.ndim}, 要素数:{T5.size}\n')
T6 = np.reshape(T,(1,2,4))
print(T6)
print('前から順に4要素2個の要素を1個')
print(f'形:{T6.shape}, 階:{T6.ndim}, 要素数:{T6.size}\n')
[[1 2 3 4]
[5 6 7 8]]
形:(2, 4), 階:2, 要素数:8
[[1 2 3 4 5 6 7 8]]
前から順に8要素のリストを1個
形:(1, 8), 階:2, 要素数:8
[[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]]
前から順に1要素のリストを8個
形:(8, 1), 階:2, 要素数:8
[[1 2]
[3 4]
[5 6]
[7 8]]
前から順に2要素のリストを4個
形:(4, 2), 階:2, 要素数:8
[1 2 3 4 5 6 7 8]
前から順に8要素
形:(8,), 階:1, 要素数:8
[[[1 2 3 4]
[5 6 7 8]]]
前から順に4要素2個の要素を1個
形:(1, 2, 4), 階:3, 要素数:8
pandas のデータフレーム
行列といえば,データフレームと見た目が似ています.
- どちらも,行の要素数が各列で同じ,列の要素数が各行で同じでなければならない.(普通の List,Tuple,Set,Dict では違う)
ということで,
- 2 階テンソル(行列)の NumpyArray は pandas データフレームに変換できます.: pandas の
DataFrame
関数
- pandas データフレームは,2 階の NumpyArray(行列)に変換できます.: pandas の DataFrame クラスの
to_numpy
メソッド
NumpyArray の行が,pandas の 1 行のサンプルに対応しています.
import pandas as pd
M = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
display(M)
columnsM = ["v0", "v1", "v2", "v3"]
indexM = ["#1", "#2", "#3"]
df = pd.DataFrame(M, columns=columnsM, index=indexM)
display(df)
# dataframe to numpy ndarray
M2 = df.to_numpy()
display(M2)
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
#1 |
1 |
2 |
3 |
4 |
#2 |
5 |
6 |
7 |
8 |
#3 |
9 |
10 |
11 |
12 |
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
pandas.read_html によるスクレイピング
ホームページや画像などからデータフレームを読み込む操作を「スクレイピング」と呼びます.
例えば,長崎の気温変化をデータフレームに読み込んでみましょう.
長崎の気温変化は,Linkにあります.
このページには,文章の中に,表がありますね.
pandas のread_html
関数は URL を指定すると,その URL のページに含まれる表の数だけデータフレームを作って,そのリストを返します. (html/xml を解釈する外部パッケージ lxml のインストールが必要です.)
python コードによるスクレイピングは,機械的な自動操作なのでホームページ作成者から敬遠される傾向があります. もし python コードから https 経由でファイルを読み込めない場合は,普段使っているウェブブラウザで手動で読み込み,ページをstats_nagasaki.html
などと名前をつけて保存し, それを読み込むように変更してください.
import lxml
from datetime import datetime
URL = "https://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=84&block_no=47817&year=&month=&day=&view=a1"
# URL = "stats_nagasaki.html"
df_List = pd.read_html(URL, header=0)
df_List[0]
0 |
1878 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
26.0 |
26.6 |
24.9 |
19.2 |
12.1 |
6.9 |
19.3 ] |
1 |
1879 |
5.7 |
7.7 |
8.8 |
14.2 |
18.6 |
21.9 |
26.6 |
27.6 |
23.3 |
17.2 |
12.1 |
8.4 |
16.0 |
2 |
1880 |
4.8 |
8.3 |
9.9 |
14.1 |
19.4 |
21.2 |
25.4 |
25.8 |
23.7 |
18.8 |
11.7 |
5.3 |
15.7 |
3 |
1881 |
4.0 |
5.9 |
7.5 |
14.1 |
18.8 |
22.3 |
26.3 |
27.8 |
24.6 |
18.2 |
13.6 |
7.9 |
15.9 |
4 |
1882 |
7.3 |
6.7 |
8.8 |
14.7 |
17.9 |
20.6 |
25.0 |
26.4 |
23.0 |
19.4 |
12.3 |
6.8 |
15.7 |
... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
142 |
2020 |
9.7 |
9.8 |
12.3 |
14.0 |
20.2 |
24.1 |
25.5 |
28.8 |
24.3 |
19.6 |
15.3 |
8.5 |
17.7 |
143 |
2021 |
7.4 |
10.3 |
13.9 |
16.7 |
19.8 |
23.7 |
27.9 |
27.3 |
25.7 |
21.1 |
14.1 |
9.2 |
18.1 |
144 |
2022 |
7.1 |
6.5 |
12.9 |
16.4 |
20.0 |
23.9 |
27.8 |
28.6 |
25.9 |
19.9 |
16.5 |
7.8 |
17.8 |
145 |
2023 |
7.4 |
9.2 |
13.6 |
16.9 |
19.7 |
23.7 |
28.0 |
29.2 |
26.7 |
19.8 |
15.1 |
10.2 |
18.3 |
146 |
2024 |
8.4 |
10.6 |
12.0 |
17.7 |
20.0 |
22.7 ] |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
13.7 ] |
147 rows × 14 columns
「年の値」の列は,その年の平均気温です.今回は使わないので消します.
各月の値は,すべて同じ尺度・分布になっていて,いわゆるwide形式なので,メルトでlong形式に直します. 年の列と,年の値の列を固定して,その他の列について,その列名を変数に,値を変数の値に整形します.
df = df_List[0].drop(columns='年の値')
df2 = df.melt(id_vars=["年"], var_name='月', value_name='気温') # pd.melt() : 先週やった pivotの逆操作
df2
0 |
1878 |
1月 |
NaN |
1 |
1879 |
1月 |
5.7 |
2 |
1880 |
1月 |
4.8 |
3 |
1881 |
1月 |
4.0 |
4 |
1882 |
1月 |
7.3 |
... |
... |
... |
... |
1759 |
2020 |
12月 |
8.5 |
1760 |
2021 |
12月 |
9.2 |
1761 |
2022 |
12月 |
7.8 |
1762 |
2023 |
12月 |
10.2 |
1763 |
2024 |
12月 |
NaN |
1764 rows × 3 columns
年の列の値と月の列を値を合成すると,年月にまとめられます.
df2["年月"] = df2["年"].astype(str) + "年" + df2["月"].astype(str)
df2
0 |
1878 |
1月 |
NaN |
1878年1月 |
1 |
1879 |
1月 |
5.7 |
1879年1月 |
2 |
1880 |
1月 |
4.8 |
1880年1月 |
3 |
1881 |
1月 |
4.0 |
1881年1月 |
4 |
1882 |
1月 |
7.3 |
1882年1月 |
... |
... |
... |
... |
... |
1759 |
2020 |
12月 |
8.5 |
2020年12月 |
1760 |
2021 |
12月 |
9.2 |
2021年12月 |
1761 |
2022 |
12月 |
7.8 |
2022年12月 |
1762 |
2023 |
12月 |
10.2 |
2023年12月 |
1763 |
2024 |
12月 |
NaN |
2024年12月 |
1764 rows × 4 columns
年月の列は,時刻データとして扱うようにします.すべて1日となります.
df2['Date'] = [datetime.strptime(s,'%Y年%m月') for s in df2['年月']]
df2
0 |
1878 |
1月 |
NaN |
1878年1月 |
1878-01-01 |
1 |
1879 |
1月 |
5.7 |
1879年1月 |
1879-01-01 |
2 |
1880 |
1月 |
4.8 |
1880年1月 |
1880-01-01 |
3 |
1881 |
1月 |
4.0 |
1881年1月 |
1881-01-01 |
4 |
1882 |
1月 |
7.3 |
1882年1月 |
1882-01-01 |
... |
... |
... |
... |
... |
... |
1759 |
2020 |
12月 |
8.5 |
2020年12月 |
2020-12-01 |
1760 |
2021 |
12月 |
9.2 |
2021年12月 |
2021-12-01 |
1761 |
2022 |
12月 |
7.8 |
2022年12月 |
2022-12-01 |
1762 |
2023 |
12月 |
10.2 |
2023年12月 |
2023-12-01 |
1763 |
2024 |
12月 |
NaN |
2024年12月 |
2024-12-01 |
1764 rows × 5 columns
整形前の列は消します.
df3 = df2.drop(columns=['年','月','年月'])
df3
0 |
NaN |
1878-01-01 |
1 |
5.7 |
1879-01-01 |
2 |
4.8 |
1880-01-01 |
3 |
4.0 |
1881-01-01 |
4 |
7.3 |
1882-01-01 |
... |
... |
... |
1759 |
8.5 |
2020-12-01 |
1760 |
9.2 |
2021-12-01 |
1761 |
7.8 |
2022-12-01 |
1762 |
10.2 |
2023-12-01 |
1763 |
NaN |
2024-12-01 |
1764 rows × 2 columns
「気温」は間隔尺度なので,floatにしたいのですが, 数字以外の記号が含まれているので,まずその記号部分を消します.
df3['気温'] = df3['気温'].astype(str)
df3['気温'] = df3['気温'].str.extract('([ 0-9.]+)')
df3["気温"] = df3["気温"].astype(float)
df3
0 |
NaN |
1878-01-01 |
1 |
5.7 |
1879-01-01 |
2 |
4.8 |
1880-01-01 |
3 |
4.0 |
1881-01-01 |
4 |
7.3 |
1882-01-01 |
... |
... |
... |
1759 |
8.5 |
2020-12-01 |
1760 |
9.2 |
2021-12-01 |
1761 |
7.8 |
2022-12-01 |
1762 |
10.2 |
2023-12-01 |
1763 |
NaN |
2024-12-01 |
1764 rows × 2 columns
NaN は,Not-A-Number(非数)を表します.欠損値ですので,気温がNaNの行は測定されていないことを表します.このような行を消します.
1 |
5.7 |
1879-01-01 |
2 |
4.8 |
1880-01-01 |
3 |
4.0 |
1881-01-01 |
4 |
7.3 |
1882-01-01 |
5 |
5.8 |
1883-01-01 |
... |
... |
... |
1758 |
10.6 |
2019-12-01 |
1759 |
8.5 |
2020-12-01 |
1760 |
9.2 |
2021-12-01 |
1761 |
7.8 |
2022-12-01 |
1762 |
10.2 |
2023-12-01 |
1751 rows × 2 columns
Date列をインデックスにして昇順に並べ直します.
df5 = df4.set_index("Date")
df5 = df5.sort_index()
df5 = df5.reset_index('Date')
df5
0 |
1878-07-01 |
26.0 |
1 |
1878-08-01 |
26.6 |
2 |
1878-09-01 |
24.9 |
3 |
1878-10-01 |
19.2 |
4 |
1878-11-01 |
12.1 |
... |
... |
... |
1746 |
2024-02-01 |
10.6 |
1747 |
2024-03-01 |
12.0 |
1748 |
2024-04-01 |
17.7 |
1749 |
2024-05-01 |
20.0 |
1750 |
2024-06-01 |
22.7 |
1751 rows × 2 columns
matplotlib
Matplotlib は,入力した系列に対応してグラフを描くライブラリです.
下のようなグラフを描きます.このグラフに書いてある用語(英単語)をそのまま覚えてください. 
Figure:フィギュア:所謂,キャンバスのことです.Figure に描かれるのは,
- 複数のグラフ(Axes)
- キャンバスのタイトル,凡例
Axes:アクセス:所謂,グラフのことです.Axes に描かれるのは,
- 軸線(Axis)
- 軸ラベル(xlabel, ylabel)
- プロット(Plot, データを使って書かれた線や点)
Axis:アクシス:所謂,軸線のことです.Axis 上に描かれるのは,
- 目盛印(Tick,ティック):目盛として軸に刻みで描かれる細い線
- 目盛文字列(Tick Label):目盛の刻みに振られる文字列(数直線ならば数字.数字でなくてもいい)
グラフを描くには,
- まず,キャンバスを用意する
from matplotlib import pyplot as plt
fig = plt.figure(figsize=(8, 12), tight_layout=True)
fig
<Figure size 768x1152 with 0 Axes>
<Figure size 768x1152 with 0 Axes>
グラフ領域を用意する: fig.add_subplot(3,2,4)
例) キャンバスを 3 行 2 列の行列状に領域を分けたときの左上から 4 番目の領域
ax1 = fig.add_subplot(3,2,4)
fig
グラフを描く
2.1. プロットを描く
```
ax1.plot(x座標の系列, y座標の系列)
```
target_year = 2024
df6 = df5[
(datetime(target_year, 1, 1) <= df5["Date"])
& (df5["Date"] < datetime(target_year + 1, 1, 1))
]
month = [m.month for m in df6["Date"]]
ax1.plot(month, df6["気温"])
fig
2.2. 軸ラベル,タイトル,凡例を補正する
```
ax1.set_xlabel('横軸ラベル')
```
ax1.set_xlabel("Month")
ax1.set_ylabel("temperature")
ax1.set_title(f"Nagasaki {target_year}")
fig
2.3. グリッド線をつけたり,表示領域を決めたり
```
ax1.set_xlim()
ax1.grid()
```
ax1.set_ylim([0,35])
ax1.set_xlim([1,12])
ax1.grid()
fig
キャンバスの別の領域に別のグラフを書く
ax2 = fig.add_subplot(3,2,1)
target_year2 = 1879
df7 = df5[
(datetime(target_year2, 1, 1) <= df5["Date"])
& (df5["Date"] < datetime(target_year2 + 1, 1, 1))
]
month2 = [m.month for m in df7["Date"]]
ax2.plot(month2, df7["気温"])
ax2.set_xlabel('Month')
ax2.set_ylabel('temperature')
ax2.set_title(f'Nagasaki {target_year2}')
ax2.set_ylim([0,35])
ax2.set_xlim([1,12])
ax2.grid()
fig
ax3 = fig.add_subplot(3, 2, 5)
target_year3 = 1945
df8 = df5[
(datetime(target_year3, 1, 1) <= df5["Date"])
& (df5["Date"] < datetime(target_year3 + 1, 1, 1))
]
month3 = [m.month for m in df8["Date"]]
ax3.plot(month, df6["気温"], "rd-", label=f"{target_year}")
ax3.plot(month2, df7["気温"], "b*:", label=f"{target_year2}")
ax3.plot(month3, df8["気温"], "g^:", label=f"{target_year3}")
ax3.set_xlabel("Month")
ax3.set_ylabel("temperature")
ax3.set_title(f"Nagasaki {target_year}, {target_year2} and {target_year3}")
ax3.set_ylim([0, 35])
ax3.set_xlim([1, 12])
ax3.legend(loc="best")
ax3.grid()
fig
箱ひげ図を書くこともできる.: boxplot
- 箱ひげ図を置く横軸目盛を
positions
で指定する
ax4 = fig.add_subplot(3, 2, 6)
ax4.boxplot(df6["気温"], positions=[1])
ax4.boxplot(df7["気温"], positions=[2])
ax4.boxplot(df8["気温"], positions=[3])
ax4.set_xlabel("Year")
ax4.set_ylabel("temperature")
ax4.set_title("boxcar plot")
ax4.set_ylim([0, 35])
ax4.set_xlim([0, 4])
ax4.set_xticklabels([f"{target_year}", f"{target_year2}", f"{target_year3}"])
ax4.grid()
fig
課題 k03
上記で紹介したリンク https://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=84&block_no=47817&year=&month=&day=&view=a1 のページには,「日平均気温」の表が見えていますが, 「日平均気温」以外にも同様の形式のデータを選ぶことができます.
降水量の表をスクレイピングし,適切に表を整形し,指定した年の月別降水量の変化をグラフ表示せよ.グラフのタイトル,横軸ラベル,縦軸ラベル,グリッドなど体裁も整えること.
いつものように,
> poetry new k03
> cd k03
> poetry add pandas matplotlib numpy lxml ipykernel
> poetry env use py
でパッケージ開発の準備をし,precipi_plot.pyをコーディングする.インタフェースプログラムは test_precipi_plot.py を実行する.
k03
┣─ k03
│ └─ precipi_plot.py
└─ tests
└─ test_precipi_plot.py
test_precip_plot.py は,precipi_plot.py の main関数を実行するものであり, 引数で,降水量変化グラフを表示したい年を指定する. poetry install --sync
を行なってからでないとテストできない.
# tests/test_precipi_plot.py
from k03 import precipi_plot as my
my.main(2024)
precipi_plot.py には,降水量変化グラフを表示する年号を引数とするmain関数を定義し,その中で降水量の表が掲載されているURLを読み込み,matplotlib, pandas を用いてグラフを表示する.
提出物は,いつものように whl ファイルであり,
> poetry version patch
> poetry build
によって,distsの下にできあがる.