乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

可視化

今回は地震データの可視化についてです
最近地震が多いので、plotlyで可視化してみました。


解説動画はこちら








データの入手先


気象庁の地震データベース

コードを動かしてみたい方は
CSVがダウンロードできるようなので
手元にダウンロードしてみてください。



データの読み込み

こちらのコードはGoogle Colabで動くようになっています。
動かしたい場合は Colabの画面左メニューから
フォルダマークをクリックすると
ファイル置き場が見えるので、そこにCSVファイルを
ドラッグなどで配置します。

ファイルを読み込みするには下記のコードです。

# 必要なライブラリのインポート
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
import warnings
warnings.filterwarnings('ignore')

file_path = "/content/地震リスト.csv"
df = pd.read_csv(file_path)
df.head()

df変数にデータが読み込まれると思います。



データの整形


CSVそのままのデータでは
うまく可視化が行えないため
可視化用にデータを加工する必要があります。

下記のコードを実行すると
可視化用のカラムなどが追加されます。


# データの前処理
s_order = ['震度1', '震度2', '震度3', '震度4', '震度5弱', '震度5強', '震度6弱', '震度6強', '震度7']
target = "トカラ列島近海"

def preprocess_data(df):
    # 緯度・経度の変換(度分秒から度へ)
    def convert_coordinate(coord_str):
        # 例: "29°28.2′N" -> 29.47
        if '°' in coord_str and '′' in coord_str:
            parts = coord_str.replace('N', '').replace('E', '').replace('S', '').replace('W', '')
            degree_part = parts.split('°')[0]
            minute_part = parts.split('°')[1].replace('′', '')
            return float(degree_part) + float(minute_part) / 60
        return float(coord_str)
    
    df['緯度_数値'] = df['緯度'].apply(convert_coordinate)
    df['経度_数値'] = df['経度'].apply(convert_coordinate)
    
    # 深さの数値化
    df['深さ_数値'] = df['深さ'].str.replace(' km', '').astype(float)
    
    # 震度の数値化
    s_mapping = {
        '震度1': 1, '震度2': 2, '震度3': 3, '震度4': 4, '震度5弱': 5,
        '震度5強': 5.5, '震度6弱': 6, '震度6強': 6.5, '震度7': 7
    }
    df['最大震度_数値'] = df['最大震度'].map(s_mapping)
    df['最大震度'] = pd.Categorical(df['最大震度'], categories=s_order, ordered=True)
    return df

# データの前処理を実行
df = preprocess_data(df)
df = df[df["震央地名"]==target]
df.head()



時系列の可視化


最初は時系列で
どれだけ地震が発生しているのかを
見てみましょう

日別、震度別で時系列で地震の回数を
表示してみます。

# 時系列分析(日付別)
df['日付'] = pd.to_datetime(df['地震の発生日'])
daily_shindo_counts = df.groupby(['日付', '最大震度']).size().reset_index(name='地震回数')

# 震度の順序を維持
daily_shindo_counts['最大震度'] = pd.Categorical(
    daily_shindo_counts['最大震度'], 
    categories=s_order, 
    ordered=True
)

# 震度別横並び棒グラフ
fig_time = px.bar(
    daily_shindo_counts,
    x='日付',
    y='地震回数',
    color='最大震度',
    title='日付別地震発生回数(震度別)',
    labels={'地震回数': '地震発生回数', '日付': '発生日'},
    category_orders={'最大震度': s_order},
    barmode='group'  # 横並びに表示
)

# レイアウトの調整
fig_time.update_layout(
    xaxis_tickangle=-45,
    height=600,
    width=1000,
    legend=dict(
        orientation="v",
        yanchor="top",
        y=1,
        xanchor="left",
        x=1.01
    )
)

fig_time.show()
スクリーンショット 2025-07-05 17.27.01

データは直近の1週間分しか無いようです。
7月前後で急増しているのが分かります。



箱ひげ図の可視化


次は箱ひげ図で
「トカラ列島近海」の詳細を見てみましょう。


震度ごとにマグニチュードをまとめると
このような感じになります。

# 震央地名 最大震度別での箱ひげ図

fig_box = px.box(df, 
                 x='震央地名', 
                 y='M',
                 color='最大震度',
                 title='震央地名別 マグニチュード分布(最大震度別)',
                 labels={'M': 'マグニチュード', '震央地名': '震央地名'},
                 category_orders={'最大震度': s_order})

fig_box.update_layout(
    xaxis_tickangle=-45,
    height=600,
    width=1000
)
fig_box.show()
スクリーンショット 2025-07-05 17.29.52
同じ震度でも、マグニチュードにはバラツキがありますが
マグニチュードが上がるにつれ、震度も大きくなっています。



地図表示

今後は地図で震度を表示してみましょう
OpenStreetMapを使用して、緯度軽度を用いて
地図に震度を表示させてみます。

# マップ表示用のデータ準備
df_map = df.copy()
df_map['緯度_数値'] = pd.to_numeric(df_map['緯度_数値'], errors='coerce')
df_map['経度_数値'] = pd.to_numeric(df_map['経度_数値'], errors='coerce')
df_map['M'] = pd.to_numeric(df_map['M'], errors='coerce')
df_map = df_map.dropna(subset=['緯度_数値', '経度_数値', 'M'])
df_map['最大震度'] = pd.Categorical(df_map['最大震度'], categories=s_order, ordered=True)

# 緯度経度と最大震度・Mを用いたmap表示(OpenStreetMap使用)
fig_map2 = go.Figure()

# 震度別に色分けしてプロット
震度_colors = {
    '震度1': '#90EE90',  # 薄緑
    '震度2': '#FFD700',  # 金色
    '震度3': '#FFA500',  # オレンジ
    '震度4': '#FF6347',  # トマト色
    '震度5弱': '#FF4500',  # 赤オレンジ
    '震度5強': '#FF0000',  # 赤
    '震度6弱': '#8B0000',  # 濃い赤
    '震度6強': '#4B0082',  # インディゴ
    '震度7': '#000000'   # 黒
}

for 震度 in s_order:
    if 震度 in df_map['最大震度'].values:
        subset = df_map[df_map['最大震度'] == 震度]
        fig_map2.add_trace(go.Scattermapbox(
            lat=subset['緯度_数値'],
            lon=subset['経度_数値'],
            mode='markers',
            marker=dict(
                size=subset['最大震度_数値'] ** 2 ,  # サイズ調整
                color=震度_colors.get(震度, '#000000'),
                sizemode='diameter'
            ),
            text=subset['震央地名'],
            hovertemplate='%{text}
' + '緯度: %{lat:.2f}
' + '経度: %{lon:.2f}
' + 'マグニチュード: %{customdata[0]}
' + '最大震度: %{customdata[1]}
' + '', customdata=list(zip(subset['M'], subset['最大震度'])), name=震度 )) fig_map2.update_layout( title='地震発生位置(最大震度・マグニチュード)- OpenStreetMap', mapbox=dict( style="open-street-map", center=dict(lat=df_map['緯度_数値'].mean(), lon=df_map['経度_数値'].mean()), zoom=8 ), height=700, width=1000 ) fig_map2.show()
スクリーンショット 2025-07-05 17.34.15

今回の地震は、かなり局所的に起きていることが分かります。
特に震度5以上が発生した箇所がかなり近く
この近辺は注意が必要な地域に見えます。


散布図表示


最後に
マグニチュード、震度、深さを
散布図で見てみましょう

# 深さとマグニチュードの関係
fig_scatter = px.scatter(
    df,
    x='深さ_数値',
    y='M',
    color='最大震度',
    size='最大震度_数値',
    hover_name='震央地名',
    hover_data={
        '地震の発生日': True,
        '地震の発生時刻': True
    },
    title='深さとマグニチュードの関係(最大震度別)',
    labels={'深さ_数値': '深さ (km)', 'M': 'マグニチュード'},
    category_orders={'最大震度': s_order}
)

fig_scatter.show()
スクリーンショット 2025-07-05 17.36.27


どの深さでも、満遍なく起きているように見えますね
地震自体は、いつ何時、どこでも起きてしまいます。



まとめ

緯度経度が記載されているデータは
plotlyなどを用いれば地図表示が比較的
簡単に行うことができます。

可視化の表現の幅が広がるので
覚えておくと良いかもしれませんね


今日はここまでです
それでは




今回は大谷選手の
ホームラン記録を可視化してみました。


解説動画はこちら



先日2024年09月20日
6打数3安打3ホームランなど
前人未到の記録を打ち立てた
ドジャース大谷選手

2004年シーズンの
今日までの成績を可視化してみました。


データの取得


可視化の元となるデータを取得します。

2024/09/21日現在
今季通算 603打数179安打 打率.297 52本塁打
122打点 125得点 52盗塁
という驚異的な成績です

この結果になるように加工していきます。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

url = "https://times.abema.tv/articles/-/10018233"

res = requests.get(url)
soup = BeautifulSoup(res.content,"lxml")
table = soup.table

columns = ['日付', '対戦相手', '打順', '第1', '第2', '第3', '第4', '第5', '第6', '第7']
data = []
trs = table.find_all("tr")
for tr in trs[1:]:
    tds = tr.find_all(["td","th"])
    if "なし" in str(tds) or "中止" in str(tds):
        continue
    tmp = [td.text.replace("\xa0","").replace("\n","").replace("\r","").replace("\t","") for td in tds]
    tmp = tmp[0:3]+[t.replace("ニ","二").replace("2","二").replace("3","三") for t in tmp[3:]]
    if len(tmp)>=11:
        tmp = tmp[:10]
    data.append(tmp)

data2 = []
for d in data:
    t1,t2 = d[0:3],d[3:]
    for t in t2:
        if ""!=t:
            data2.append(t1+[t])

# データの確認
data2[0:2]
[['3月20日', 'パドレス', '2指', '遊ゴ'], ['3月20日', 'パドレス', '2指', '右安']]


これを集計しやすいようにデータフレームに加工していきます。
df = pd.DataFrame(data2,columns=["date","match","order","result"])

# 日本語の日付を変換する関数
def convert_date(date_str):
    month,day = date_str.replace("日","").split("月")
    return f"2024-{int(month):02}-{int(day):02}"

df['date'] = df['date'].apply(convert_date)
df['date'] = pd.to_datetime(df['date'])

# 本塁打
df['home_run'] = df['result'].apply(lambda x: 1 if '本' in x else 0)

# 安打
df['hit1'] = df['result'].apply(lambda x: 1 if '安' in x else 0)

# 2塁打
df['hit2'] = df['result'].apply(lambda x: 1 if x.endswith('二') else 0)

# 3塁打
df['hit3'] = df['result'].apply(lambda x: 1 if x.endswith('三') else 0)

# 指定された用語のリスト
terms = ['四球', '敬遠', '死球', '打妨', '中犠', '右犠', '左犠']

# 打数計算
df['flag'] = df['result'].apply(lambda x: 0 if x in terms else 1)

# 打席数合計
df['strokes'] = df['flag'].cumsum()

# ヒット数合計
df['hit_total'] = df[['hit1','hit2','hit3']].sum(axis=1).cumsum()

# ホームラン数合計
df['home_run_total'] = df['home_run'].cumsum()

# 打率
df['average'] = df[['hit_total','home_run_total']].sum(axis=1) / df['strokes']

# 603打数179安打 打率.297 52本塁打
df.tail()
index,date,match,order,result,home_run,hit1,hit2,hit3,flag,strokes,hit_total,home_run_total,average
688,2024-09-20 00:00:00,マーリンズ,1指,右本,1,0,0,0,1,599,125,51,0.2938230383973289
689,2024-09-21 00:00:00,ロッキーズ,1指,空振,0,0,0,0,1,600,125,51,0.29333333333333333
690,2024-09-21 00:00:00,ロッキーズ,1指,中安,0,1,0,0,1,601,126,51,0.2945091514143095
691,2024-09-21 00:00:00,ロッキーズ,1指,中本,1,0,0,0,1,602,126,52,0.2956810631229236
692,2024-09-21 00:00:00,ロッキーズ,1指,一安,0,1,0,0,1,603,127,52,0.296849087893864

これで同じ結果になりました。



Altairでプロットする

ここからこのデータを用いて
可視化を行います。

import pandas as pd
import altair as alt

# Altairで折れ線グラフを作成
chart = alt.Chart(df).mark_line().encode(
    x='date',
    y='average'
).properties(
    title='Data vs Average',
    width=800
)
chart.display()
visualization (1)



打率とホームラン数の推移を
2軸でプロットするとこうなります。
# averageの折れ線グラフ
average_line = alt.Chart(df).mark_line(color='blue').encode(
    x='date',
    y=alt.Y('average', axis=alt.Axis(title='Average'))
)

# home_run_totalの折れ線グラフ
home_run_line = alt.Chart(df).mark_line(color='red').encode(
    x='date',
    y=alt.Y('home_run_total', axis=alt.Axis(title='Home Run Total'))
)

# 二重軸のグラフを作成
chart = alt.layer(
    average_line,
    home_run_line
).resolve_scale(
    y='independent'
).properties(
    title='Data vs Average and Home Run Total',
    width=800
)
chart.display()
visualization


途中から急にホームラン数のピッチが上がっているようです。

ここからどれだけ伸びるのか
残り試合のホームラン数の予測もしてみます。


ホームラン数を時系列予測

残り試合は8試合です。
ARIMAという時系列予測モデルを用いて
予測をしてきます。

先ほどのデータフレームを加工して
予測データを作っていきます。

import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt

# 日付をインデックスに設定
df2 = df.copy()
df2 = df.groupby('date').max()

# 欠損日を埋めて連続した日付にする
df2 = df2.asfreq('D')

# ARIMAモデルのフィッティング
model = ARIMA(df2['home_run_total'], order=(1, 1, 1))
model_fit = model.fit()

# 8日先までの予測
forecast = model_fit.forecast(steps=8)

# 結果のプロット
plt.figure(figsize=(12, 4))
plt.plot(df2.index, df2['home_run_total'], label='Actual')
plt.plot(pd.date_range(df2.index[-1] + pd.Timedelta(days=1), periods=8, freq='D'), forecast, label='Forecast', color='red')
plt.legend()
plt.show()
download

実測と予測を組み合わせて可視化しました。

残り8試合だとすると
およそ55本くらいまでは伸びそうです。

予測が外れて60本塁打くらいまで
行って欲しいですね!!!!

今回は大谷選手の打席結果を用いた
ホームラン成績の可視化でした

それでは。

今回はインタラクティブな可視化が行えるライブラリ
Altairのご紹介です。


解説動画はこちら



Altairライブラリについて

少ないコードで複雑なビジュアライゼーションを作成できる
可視化ライブラリです。

静止画でなくインタラクティブな
ビジュアライゼーションを行うことができます。

Pandasデータフレームと相性が良く
ダッシュボード制作などで活用されるケースが増えています。



他の可視化ライブラリとの違い

スクリーンショット 2024-08-10 15.11.51

Altairの他にも沢山の可視化ライブラリがあります。

その中でもMatplolibは定番の可視化ライブラリですが
ドキュメントの数量は多く、全てを覚えるのはなかなか大変です。

AltairはPlotlyのようなインタラクティブな可視化が出来る事と
学習コストがかなり少ない事が大きな利点です。

可視化の目的によって
これらのライブラリを使い分けると良いでしょう。


Altairの使い方

1.ライブラリのインポート

Google ColabではそのままAltairを利用できます。
import pandas as pd
import numpy as np
import altair as alt
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)

その他、必要なライブラリを読み込んでおきましょう。


2.データの用意

適当なデータをPandasデータフレームで作成しておきます。
# サンプルデータの作成
data = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [2, 3, 4, 5, 6]
})

3.チャートの作成

Altairの基本的なチャートは、alt.Chartオブジェクトを使用して作成します。

mark_*メソッドでマーク(グラフの種類)を指定し
encodeメソッドでデータのエンコード(軸、色、大きさなど)を
指定する構文になっています。

チャート変数 = alt.Chart(データ変数).mark_point().encode(
  エンコード内容
)

# チャートの作成
chart = alt.Chart(data).mark_point().encode(
    x='x',
    y='y'
)

# チャートの表示(Jupyter Notebook環境などで)
chart
visualization (9)

指定できるmark_*メソッド例として

mark_point:ポイントマーク(散布図)
mark_line:折れ線グラフ
mark_bar:棒グラフ
mark_circle:バブルチャート
mark_rect:矩形マーク(ヒートマップなど)
mark_boxplot:箱ひげ図
mark_errorband:エラーバンド
mark_errorbar:エラーバー
mark_geoshape:地理シェープ(地図)
mark_image:画像マーク
mark_area:エリアチャート
mark_rule:ルールマーク(基準線など)
mark_square:四角形マーク(散布図の一種)
mark_text:テキストマーク
mark_tick:ティックマーク

こんな感じのグラフを作成できます。


4. エンコーディング

encodeメソッドを使用して
データを視覚的な属性にマッピング出来ます。

x : x軸
y : y軸
color : 色
size : サイズ
shape : 形
tooltip : ホバー時に表示されるツールチップ
data = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [2, 3, 4, 5, 6],
    'category': ['A', 'B', 'A', 'B', 'A']
})

chart = alt.Chart(data).mark_point().encode(
    x='x',
    y='y',
    color='category',
    tooltip=['x', 'y', 'category']
)
chart

5.インタラクティブ性の追加

interactiveメソッドを使用して
グラフにインタラクティブ機能(ズーム、パンなど)を
追加できます。
chart = alt.Chart(data).mark_point().encode(
    x='x',
    y='y',
    color='category'
).interactive()
chart

6.レイヤリングと複合グラフ

Altairでは複数のチャートをレイヤリングして
重ね合わせることも可能です。

+演算子を使用してチャートを合成します。

points = alt.Chart(data).mark_point().encode(
    x='x',
    y='y',
    color='category'
)

lines = alt.Chart(data).mark_line().encode(
    x='x',
    y='y',
    color='category'
)

combined_chart = points + lines
combined_chart


Altairグラフのコードサンプル

散布図
# サンプルデータの作成
np.random.seed(42)
data = pd.DataFrame({
    'x': np.random.randn(100),
    'y': np.random.randn(100),
    'category': np.random.choice(['A', 'B', 'C'], 100)
})

# インタラクティブな散布図の作成
scatter_plot = alt.Chart(data).mark_point().encode(
    x='x',
    y='y',
    color='category',
    tooltip=['x', 'y', 'category']
).interactive()
scatter_plot
visualization (5)

棒グラフ
# サンプルデータの作成
data = pd.DataFrame({
    'category': ['A', 'B', 'C', 'D', 'E'],
    'value': [5, 3, 6, 7, 2]
})

# インタラクティブな棒グラフの作成
bar_chart = alt.Chart(data).mark_bar().encode(
    x='category',
    y='value',
    tooltip=['category', 'value']
).interactive()
bar_chart
visualization (4)

折れ線グラフ
# サンプルデータの作成
data = pd.DataFrame({
    'date': pd.date_range(start='2023-01-01', periods=100),
    'value': np.random.randn(100).cumsum()
})

# インタラクティブな折れ線グラフの作成
line_chart = alt.Chart(data).mark_line().encode(
    x='date:T',
    y='value:Q',
    tooltip=['date:T', 'value:Q']
).interactive()
line_chart
visualization

ヒートマップ
# サンプルデータの作成
data = pd.DataFrame({
    'x': np.repeat(np.arange(1, 11), 10),
    'y': np.tile(np.arange(1, 11), 10),
    'value': np.random.randn(100)
})

# インタラクティブなヒートマップの作成
heatmap = alt.Chart(data).mark_rect().encode(
    x='x:O',
    y='y:O',
    color='value:Q',
    tooltip=['x', 'y', 'value']
).interactive()
heatmap
visualization (2)

バブルチャート
# サンプルデータの作成
np.random.seed(42)
data = pd.DataFrame({
    'x': np.random.randn(100),
    'y': np.random.randn(100),
    'size': np.random.rand(100) * 100,
    'category': np.random.choice(['A', 'B', 'C'], 100)
})

# インタラクティブなバブルチャートの作成
bubble_chart = alt.Chart(data).mark_circle().encode(
    x='x',
    y='y',
    size='size',
    color='category',
    tooltip=['x', 'y', 'size', 'category']
).interactive()
bubble_chart
visualization (1)

ヒストグラム
# データの生成
np.random.seed(42)
data = pd.DataFrame({
    'value': np.random.normal(loc=0, scale=1, size=1000)
})

# ヒストグラムの作成
histogram = alt.Chart(data).mark_bar().encode(
    alt.X('value:Q', bin=True, title='Value'),
    alt.Y('count()', title='Frequency')
).interactive()
histogram
visualization (12)

箱ひげ図
# データの生成(例: 1000個のランダムな数値を3つのカテゴリに分ける)
np.random.seed(42)
data = pd.DataFrame({
    'category': np.random.choice(['A', 'B', 'C'], size=1000),
    'value': np.random.normal(loc=0, scale=1, size=1000)
})

# 箱ひげ図の作成
boxplot = alt.Chart(data).mark_boxplot().encode(
    x='category:O',
    y='value:Q'
).interactive()
boxplot
visualization (11)

エリアチャート
# データの生成
data = pd.DataFrame({
    'year': [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007],
    'value': [10, 15, 13, 17, 20, 19, 15, 10]
})

# エリアチャートの作成
areachart = alt.Chart(data).mark_area().encode(
    x='year:O',
    y='value:Q'
).interactive()
areachart
visualization (10)




まとめ

Altairは可視化ライブラリの中でも比較的学習コストが低く
かなり使い勝手の良い可視化ライブラリです。

特にインタラクティブな可視化を行えるのが魅力的なライブラリで
最近はデータサイエンスやデータ分析の分野で広く採用されて来ています。

WEBアプリを作成できるStreamlitライブラリと相性が良く
簡易なアプリ作成やダッシュボード作成などにも向いています。

Matplotlibの可視化に物足りなくなって来たら
こちらのライブラリはオススメです。

それでは。





最近、やけに地震が多いので
気になって調べて
データを取って可視化してみました。


解説動画はこちら


 
今回のコードは
気象庁の地震データを取得して
そのデータを可視化するコードです。

先にデータが必要になるので
気になった人はコードを実行してみて下さい。

Jupyter Notebook や Google Colab で
実行できると思います。


地震データを取得する



気象庁から地震の観測データを取得して
データフレームにします。

やりたい人は、30分くらいかかるので
気をつけてやって下さい。

リンクを取得
import re
import time
import pandas as pd
import requests
from tqdm import tqdm
from bs4 import BeautifulSoup

domain = "https://www.data.jma.go.jp/"
index_url = "https://www.data.jma.go.jp/svd/eqev/data/daily_map/index.html"
res = requests.get(index_url)
soup = BeautifulSoup(res.content, "html.parser")
eq_link = [i.get("href") for i in soup.find_all("a") if len(i.get("href"))==13]
print(len(eq_link))

リンクからデータを取得する
# 地震データをテキストから取得
def data_pick(text):
    row_data = []
    for row in [i for i in text.split("\n") if len(i)>1][2:]:
        row = row.replace("° ","°")
        for i in range(7,1,-1):
            row = row.replace(" "*i, " ")
        row = row.replace(":"," ")
        tmp = row.split(" ")
        row_data.append(tmp[:-1])
    return row_data

all_data = []
for day in tqdm(eq_link):
    url = "https://www.data.jma.go.jp/svd/eqev/data/daily_map/" + day
    #print(url)
    res = requests.get(url)
    soup = BeautifulSoup(res.content, "html.parser")
    time.sleep(3.971)
    text_data = soup.pre.text
    all_data += data_pick(text_data)
    #break

print(len(all_data))


データを加工してデータフレームにする
columns=["年","月","日","時","分","秒","緯度","経度","深さ(km)","M","震央地名"]
df = pd.DataFrame(all_data,columns=columns)
df = df[df["M"] != "-"].reset_index(drop=True)
df = df.astype({"M": float , "深さ(km)":int, "年":int,"月":int,"日":int})
df["M2"] = df["M"]//1
df["年月日"] = df.apply(lambda x : "{0}{1:02}{2:02}".format(x["年"],x["月"],x["日"]),axis=1)

def lat_lon_10(x):
    tmp = x.split("°")
    degree = int(tmp[0])
    minute = int(tmp[1].split(".")[0])
    second = int(tmp[1].split(".")[1].split("'")[0])
    # 度、分、秒を10進法で表現
    decimal_degree = degree + minute/60 + second/3600
    return decimal_degree

df["緯度10"] = df["緯度"].map(lat_lon_10)
df["経度10"] = df["経度"].map(lat_lon_10)
df["size"] = df["M2"] ** 3

うまく実行できたら
df という変数にデータが格納されると思います。

エラーが出る人は
ライブラリが足りなかったり
通信がうまくいっていなかったり
色々な事象が起きると思いますが
全部は対応出来ませんので
がんばって解決して下さい。


データを見てみる


データを取得できたら
データを見てみましょう。

変数 df に対して
条件を指定すれば
絞り込みをしてみる事ができます。

マグニチュード別の地震回数
df[["M2","震央地名"]].groupby("M2").count().sort_index(ascending=False)
スクリーンショット 2024-03-02 16.29.41

日本地図にプロットする


日本地図にプロットするには
緯度と経度が必要です。

df 変数にはカラムとして
作成しているので
地図に表示する事ができます。

import plotly.express as px

fig = px.scatter_mapbox(
    data_frame=df[df["M2"]>=5],
    lat="緯度10",
    lon="経度10",
    hover_data=["年月日","深さ(km)"],
    color="M2",
    size="size",
    size_max=20,
    opacity=0.3,
    zoom=4,
    height=700,
    width=1500)

fig.update_layout(mapbox_style='open-street-map')
fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
fig.show()
output_88_0




千葉県だけにすると
こんな感じです。

output_106_0


色々な条件で絞り込んで
地図に反映させる事ができます。

最近は地震が多いので
何かが起きている可能性がありますね。

警戒を強める意味でも
データを見る価値はあるかもしれません。

今回は地震のデータを取得して
それを可視化するコードについてでした。

それでは。


先日結構大きな地震が有ったので
気になってしまいました。

地震のデータを集めてみたので
可視化することにしました。

解説動画はこちら



さて今回参考にしたのは
気象庁のデータです。

地震データベースがあるので
そこからCSVでダウンロードできるようです。

観測当時からのデータがありますが
震度1とかにしてしまうと
データが多すぎて全件は無理そうでした。

今回は震度5弱以上で抽出し
地震リスト.tsvに保存しています。

早速データを読み込みします。
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

data1_path = '地震リスト.tsv'
df1 = pd.read_table(data1_path)

中身はこんな感じになっています。

スクリーンショット 2021-10-09 16.57.00

つい最近までの震度5弱以上の震源地が記載されています。


これを可視化してみます。

可視化にはFoliumを使用します。
これはデータを地図上にプロットして
見やすくしてくれるものです。

マッピングには緯度経度を使用します。
データだと緯度経度の値などが
文字列かつ形式も違うので
そのままではマッピングに適していません。

DMS形式の緯度経度を
Degree形式の緯度経度に直します。

その他数値であるデータもデータ型を直す
前処理を行うコードがこれです。

# 不明データの削除
df1 = df1[~(df1['緯度']=='不明') | ~(df1['経度']=='不明')]
df1 = df1[~(df1['M']=='不明')]
df1 = df1.reset_index(drop = True)

# 震度データの整理
df1['最大震度'][df1['最大震度']=='震度5'] = '震度5弱'
df1['最大震度'][df1['最大震度']=='震度6'] = '震度6弱'

# 数値データの整形
df1['緯度2'] = df1['緯度'].str.replace('′N','').str.replace('°','.').str.split('.')
df1['経度2'] = df1['経度'].str.replace('′E','').str.replace('°','.').str.split('.')
df1['深さ'] = df1['深さ'].str.replace(' km','')

# 緯度経度をDegree形式へ変換
import math
from decimal import Decimal, ROUND_HALF_UP

# DMS形(度分秒)からDegree形式(度)への変換
def dms_to_degree(d):
    h,m,s = int(d[0]),int(d[1]),int(d[1])
    return Decimal(str(h + (m / 60) + (s / 3600))).quantize(Decimal('0.0001'), rounding=ROUND_HALF_UP)
    
# 緯度経度の変換
df1['longitude'] = df1['緯度2'].apply(dms_to_degree)
df1['latitude'] = df1['経度2'].apply(dms_to_degree)

# データ型変換
df1['longitude'] = df1['longitude'].astype('float')
df1['latitude'] = df1['latitude'].astype('float')
df1['M'] = df1['M'].astype('float')
df1['深さ'] = df1['深さ'].astype('int')

これで下処理ができました。
出来上がりはこんな感じです。

スクリーンショット 2021-10-09 16.58.15


早速このデータを使って
Foliumでマッピングしたいと思います。

今回は震度別で色で分けられるように
マッピングしました。

データは前処理したdf1をそのまま使用します。

import folium
from folium.features import CustomIcon
import pandas as pd

# マーカーの指定
def make_maker(r,color):
    rad = r['M'] * 1000
    name = r['地震の発生日'] + ' ' + r['地震の発生時刻'] +' : ' + r['震央地名']
    tmp = folium.Circle(
        location = [r['longitude'],r['latitude']], 
        popup    = name ,
        tooltip  = '{0} , 深さ : {1}km , M{2} , {3}'.format(name,r['深さ'],r['M'],r['最大震度']),
        radius = rad , color     = color , fill = True )
    return tmp
    
plot_map = folium.Map(location=[適当な緯度,適当な経度] ,  zoom_start=5)
s5j_group = folium.FeatureGroup(name="震度5弱").add_to(plot_map)
s5k_group = folium.FeatureGroup(name="震度5強").add_to(plot_map)
s6j_group = folium.FeatureGroup(name="震度6弱").add_to(plot_map)
s6k_group = folium.FeatureGroup(name="震度6強").add_to(plot_map)
s7_group = folium.FeatureGroup(name="震度7").add_to(plot_map)

for i, r in df1[df1['最大震度']=='震度5弱'].iterrows():
    s5j_group.add_child(make_maker(r,'#99FFFF'))
for i, r in df1[df1['最大震度']=='震度5強'].iterrows():
    s5k_group.add_child(make_maker(r,'#66FF00'))
for i, r in df1[df1['最大震度']=='震度6弱'].iterrows():
    s6j_group.add_child(make_maker(r,'#FFFF00'))
for i, r in df1[df1['最大震度']=='震度6強'].iterrows():
    s6k_group.add_child(make_maker(r,'#FF00FF'))
for i, r in df1[df1['最大震度']=='震度7'].iterrows():
    s7_group.add_child(make_maker(r,'#FF0000'))
folium.LayerControl().add_to(plot_map)
plot_map.save("HTMLファイル名")

実行するとHTMLファイルが出来上がります。
それを開くとこんな感じです。

スクリーンショット 2021-10-09 16.52.44


地図の右上から震度別で
表示を切り替えできるようになっています。

スクリーンショット 2021-10-09 16.54.24

震度6以上だとこんな感じに

スクリーンショット 2021-10-09 16.54.51

震度7だけだとこうなります。
スクリーンショット 2021-10-09 16.55.15


先日地震のあった場所は
千葉県の北西部で震度5強を観測しています。

スクリーンショット 2021-10-09 17.05.12


こんな感じでうまくマッピング出来て
いろいろ切り替えながら眺められるので
面白いと思います。

50年以上の観測データのようですが
関東圏では震度6強より大きな地震の発生がなさそうですね。

ただ、ここ最近の大地震を線で結ぶと
スクリーンショット 2021-10-09 16.54.51のコピー

きれいに一直線でつながるのが
少し怖いですねーー

無理くりつなげてみましたが
このライン上には何か有るのかも・・・

いつ地震が来ても良いような
備は考えておくべきですね!!!!

今回は地震データを
Folium可視化してみました。

やりたい方はコピペして
試してみてくださいね。

それでは。

このページのトップヘ