Numpy

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)
[[0. 0. 0.]
 [0. 0. 0.]]

行列の形は 属性.shapeに保存されています.

print(M.shape)
(2, 3)

形(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)
[[1 2 3]
 [4 5 6]]
(2, 3)

では,7 と 8 と 9 を要素にもつ 1 行 3 列の NumpyArray を作ってみましょう.行ごとにリストに,全体をさらにリストにします.つまり 2 重リストになります.

Lx = [[7,8,9]]
x = np.array(Lx)
print(x)
print(x.shape)
[[7 8 9]]
(1, 3)

この x を転置すれば,Mx が計算できます.転置は Numpy のtranspose関数を使います.また,行列積は Numpy のmatmul関数です.(ちなみに*は要素積(要素同士の積)です.)

R = np.matmul(M, np.transpose(x))
print(R)
print(R.shape)
[[ 50]
 [122]]
(2, 1)

皆が知っているように,行列積は,「左の行列の列数」と「右の行列の行数」が同じでなければいけません.上の例の場合,(2,3)と(3,1)なので行列積は(2,1)となるわけですね.

ところで,x は行列でしょうか?ベクトルでしょうか?

行ベクトルとか列ベクトルと言われているものですが,Numpy では 「行数」とか「列数」とか言っているので,これは「行列」なのです.では,ベクトルは?

ベクトルとは,1 重のリストから作ったものをいいます.「行」や「列」という概念がありません.

Ly = [7,8,9]
y = np.array(Ly)
print(y)
print(y.shape)
[7 8 9]
(3,)

さきほどの x は形が(1,3)で,転置したnp.transpose(x)は(3,1)でした.今度の y は(3,)です.さきほどの行列積ができます.

R2 = np.matmul(M, y)
print(R2)
print(R2.shape)
[ 50 122]
(2,)

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)
(3,)
(3,)

テンソル

行列やベクトルをまとめて,一般的に「テンソル」と言います.

さきほどの「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]])
v0 v1 v2 v3
#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]
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月 年の値
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
気温 年月 Date
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
気温 Date
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
気温 Date
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の行は測定されていないことを表します.このような行を消します.

df4 = df3.dropna()
df4
気温 Date
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
Date 気温
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):目盛の刻みに振られる文字列(数直線ならば数字.数字でなくてもいい)

グラフを描くには,

  1. まず,キャンバスを用意する
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>
  1. グラフ領域を用意する: fig.add_subplot(3,2,4)

    例) キャンバスを 3 行 2 列の行列状に領域を分けたときの左上から 4 番目の領域

ax1 = fig.add_subplot(3,2,4)
fig

  1. グラフを描く

    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の下にできあがる.