Python で Tweet の位置情報をヒートマップを用いて可視化しよう！(cartopy版)

# はじめに
[「Python で Twitter の位置情報収集」](https://qiita.com/ryamamoto0406/items/82e71076af387ee77762)で集めた Tweet の位置情報を可視化していきたい。今回はキーワードを "beer" として 2022年5月23日、24日、25日の間につぶやかれた位置情報を取得し、これを可視化してみる。データの取得方法については上記記事を確認されたし。

# データの読み込みと結合
まず収集したデータを読み込み、三つのデータフレームを結合する。

```python:データ読み込みと結合
# 必要なモジュールのインポート
import pandas as pd

# データの読み込み
df1 = pd.read_csv("./20220523_beer.csv")
df2 = pd.read_csv("./20220524_beer.csv")
df3 = pd.read_csv("./20220525_beer.csv")

# データ結合
df = pd.concat([df1, df2, df3])

# 結果の表示
df.head()
```

出力は下記の通り。

![cartopy_heatmap_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/1ddbd79b-ecb7-ddcd-8ef7-1b06d1cf808c.png)


データフレームのサイズを確認してみると、

```python:データセット確認
# サイズ確認
print(df.shape)
```

```python:出力
(5788, 5)
```

約5800行程度の位置情報がある。

# cartopy を用いた単純な可視化
まずは何も考えずデータを可視化してみる。

```python:単純な散布図による可視化
# オブジェクトの作成
fig = plt.figure(figsize=(10, 10))

# プロット範囲
crs = ccrs.PlateCarree()
extent = (-180.0, 180.0, -90.0, 90.0)

# 世界地図の設定
ax = plt.axes(projection=crs)
ax.set_extent(extent, crs)
ax.add_feature(cfea.OCEAN, color="#f5f5f5")
ax.add_feature(cfea.LAND, color="#d3d3d3")
# ax.coastlines(resolution="110m")
ax.gridlines(draw_labels=True, color="#ffffff")

# 位置情報のプロット
ax.scatter(
    df["long"], df["lat"], color="blue", s=10, marker="o",
)

# 保存
fig.savefig(
    "simple_plot.png", bbox_inches="tight",
)

plt.show()
```

![simple_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/1fe043af-45e3-d7dd-480f-beac708dd8bd.png)

点が重なっていて、つぶやきの件数がどの程度多いのか見当がつかない。ので、これをヒートマップを用いて可視化する。

# cartopy でヒートマップを作る。
## 緯度経度のクロス集計表の作成
まず、下準備として緯度経度からなるクロス集計表を作ることが必要となる。そのための処理をする関数を以下に示す。

```python:クロス集計表作成用の関数
# 必要なモジュールのインポート
import numpy as np

# 以下、関数群
# DataFrame に値が存在しているかの判定用関数
def check_data(df, x, y):
    try:
        df.loc[x, y]
        return True
    except KeyError:
        return False

# データが無い所をゼロで埋める関数
def fill_zero_crosstab(df_crosstab, lat_label, long_label):
    for lat in lat_label:
        for long in long_label:
            if check_data(df_crosstab, lat, long):
                if np.isnan(df_crosstab.loc[lat, long]):
                    df_crosstab.loc[lat, long] = 0
                else:
                    pass
            else:
                df_crosstab.loc[lat, long] = 0
    
    return df_crosstab

# クロス集計用のビンを作成する関数
def make_latlong_bin(bins):
    # 例外判定
    if ((180 % bins) != 0) or ((360 % bins) != 0):
        raise ValueError("error!")
    
    # bin の作成
    lat_bin = np.linspace(-90.0, 90.0, int((180/bins)+1))
    long_bin = np.linspace(-180.0, 180.0, int((360/bins)+1))
    
    return lat_bin, long_bin

# クロス集計用のラベルを作成する関数
def make_latlong_label(bins):
    # 例外判定
    if ((180 % bins) != 0) or ((360 % bins) != 0):
        raise ValueError("error!")
        
    # label の作成
    lat_label = np.linspace((-90.0+(bins/2)), (90.0-(bins/2)), int(180/bins))
    long_label = np.linspace((-180.0+(bins/2)), (180.0-(bins/2)), int(360/bins))
    
    return lat_label, long_label

# 緯度経度のクロス集計表を作成する関数
def make_crosstab(df, bins, col_lat, col_long):
    # ビンの作成
    lat_bin, long_bin = make_latlong_bin(bins)
    
    # label の作成
    lat_label, long_label = make_latlong_label(bins)
    
    # 緯度の binning
    df_lat = pd.cut(df[col_lat], bins=lat_bin, retbins=False, labels=lat_label)
    
    # 経度の binning
    df_long = pd.cut(df[col_long], bins=long_bin, retbins=False, labels=long_label)
    
    # クロス集計表
    df_crosstab = pd.crosstab(df_lat, df_long)
    
    # ゼロで埋める
    df_crosstab = fill_zero_crosstab(df_crosstab, lat_label, long_label)
    
    # index をソート
    df_crosstab.sort_index(inplace=True)
    df_crosstab.sort_index(axis=1, inplace=True)
    
    return df_crosstab
```

これらの関数をつかって、緯度経度のデータから緯度経度毎のクロス集計表を作成する。

```python:クロス集計表の作成
# 緯度経度を何度毎にビニングするかの指定
bins = 5

# クロス集計表の作成
df_crosstab = make_crosstab(df, bins, "lat", "long")

# 結果の表示
df_crosstab.head()
```

![cartopy_heatmap_data_crosstab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/668aa5e9-1b18-143d-8643-3ead931de1e6.png)


これでヒートマップ作成の下準備ができた。

## ヒートマップによる可視化
以下の関数を使ってヒートマップを作成する。

```python:ヒートマップ作成用関数
# 必要なモジュールのインポート
import matplotlib.colors as colors
from matplotlib import colorbar, gridspec
from matplotlib.colors import Normalize

# カラーマップ作成用関数（0の点を透明にする）
def make_cmap(cmap_name):
    cmap = plt.cm.get_cmap(cmap_name)
    cmap_data = cmap(np.arange(cmap.N))
    cmap_data[0, 3] = 0 # 0のときのα値を0(透明)にする
    customized = colors.ListedColormap(cmap_data)
    
    return customized

# カラーバーの作成用関数
def plot_cbar(ax, label, cmap, norm):
    cbar = colorbar.ColorbarBase(
        ax=ax,
        cmap=cmap,
        norm=norm,
        orientation="vertical",
        label=label,
    )

# ヒートマップ描画用関数
def plot_heatmap(ax, df_crosstab, bins, cmap, norm):
    # bin の作成
    lat_bin, long_bin = make_latlong_bin(bins)
    
    # メッシュの作成
    Long, Lat = np.meshgrid(long_bin, lat_bin)
    
    # プロットの作成
    ax.pcolormesh(Long, Lat, df_crosstab, cmap=cmap, norm=norm)
```

これらの関数を用いて、ヒートマップを作成する。

```python:ヒートマップの作成
# カラーマップ作成
cmap = make_cmap("jet")

# 地図情報の読み込み
crs = ccrs.PlateCarree()
extent = (-180.0, 180.0, -90.0, 90.0)

# プロットの設定
fig = plt.figure(figsize=(10, 5))
spec = gridspec.GridSpec(ncols=2, nrows=1, width_ratios=[50, 1])
ax = fig.add_subplot(spec[0], projection=crs)
cb = fig.add_subplot(spec[1])

# 世界地図の設定
ax.set_extent(extent, crs)
ax.add_feature(cfea.OCEAN, color="#f5f5f5")
ax.add_feature(cfea.LAND, color="#d3d3d3")
# ax.coastlines(resolution="110m")
ax.gridlines(draw_labels=True, color="#ffffff")

# Normalize
vmin = df_crosstab.min().min()
vmax = df_crosstab.max().max()
norm = Normalize(vmin, vmax)

# ヒートマップ作成
plot_heatmap(ax, df_crosstab, bins, cmap, norm)

# カラーバー
plot_cbar(cb, "Number", cmap, norm)

# 図の保存
fig.savefig(
    "./cartopy_heatmap.png", bbox_inches="tight",
)

plt.show()
```

![cartopy_heatmap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/bf843586-cdd9-35e1-dcf5-236ccfdd2bff.png)


以上がヒートマップによる位置情報付き Tweet の可視化である。やはりアメリカ、ヨーロッパ、日本で多く beer とつぶやかれており、特にロンドン、ニューヨーク付近で色が赤に近くなっているのがわかると思う。

# おわりに
少々手間はかかるが matplotlib + cartopy で位置情報付き Tweet をヒートマップで可視化することができた。次はこの情報を元に、ヒートマップの時間変化がわかるようなアニメーションを作成したいと思う。（記事としては次々回の予定。）興味があればそちらも読んで頂けると有り難い。