Pythonで100%積み上げ棒グラフを描く

個人的な好みの話ですが、僕は割合の可視化は円グラフより100%積み上げ棒グラフの方が好みです。前職時代はこの種の可視化はもっぱらTableauで描いていたのですが転職して使えなくなってしまったので、Pythonでサクッと描ける方法をメモしておきます。

最初、matplotlibのbarやbarhでbottomやleftを逐一指定してカテゴリごとに描いていく方法を紹介しようと思っていたのですが、pandas.DataFrameのメソッドを使った方が簡単だったのでそちらを先に紹介します。

データの準備

サンプルデータが必要なので適当にDataFrameを作ります。地域ごとに、どの製品が売れているか、みたいなイメージのデータです。

import pandas as pd


data = {
    '製品A': [20, 30, 50],
    '製品B': [60, 70, 40],
    '製品C': [20, 0, 10]
}
df = pd.DataFrame(data, index=["地域1", "地域2", "地域3"])

print(df)
"""
     製品A  製品B  製品C
地域1   20   60   20
地域2   30   70    0
地域3   50   40   10
"""

こちらを地域別に、売れている製品の内訳を可視化していきます。
これも個人的な好みの話ですが、棒グラフは縦向きではなく横向きにします。(ラベルが日本語だとその方が自然な結果になりやすいからです。)

シンプルにやるには、jupyter環境であれば以下のコードだけで目的のグラフが表示されます。

# データの正規化
df_normalized = df.div(df.sum(axis=1), axis=0)
# 横向きの積み上げ棒グラフの描画
df_normalized.plot(kind='barh', stacked=True)

少し脇道に逸れますが、みなさん、この行ごとに正規化するdf.div(df.sum(axis=1), axis=0) ってやり方ご存知でした?昔、「Pandasのデータを割合に変換する」って記事を書いたときは知らなかった方法でした。このときは転置して列ごとに正規化してもう一回転置するという手順を踏んでましたね。

記事の本題に戻ると、plotメソッドのstackedっていう引数をTrueにしておくと棒グラフを積み上げて表示してくれるため目的のグラフになります。
参考: pandas.DataFrame.plot.barh — pandas 2.1.3 documentation

ただ、これで出力すると、グラフの棒の並び順が上から順に地域3, 地域2, 地域1 となり、ちょっと嫌なのと、あとラベル等のカスタマイズも行えた方が良いと思うのでもう少し丁寧なコードも紹介しておきます。(こだわりなければさっきの2行で十分です。)

import matplotlib.pyplot as plt


# データの正規化
df_normalized = df.div(df.sum(axis=1), axis=0)
# 行の順序を逆にする
df_normalized = df_normalized.iloc[::-1]

# 横向きの積み上げ棒グラフの描画
fig = plt.figure(facecolor="w")
ax = fig.add_subplot(111)
df_normalized.plot(kind="barh", stacked=True, ax=ax)

# タイトルとラベルの設定(任意)
ax.set_title("地域別の販売製品割合")
ax.set_ylabel("地域")
ax.set_xlabel("割合")
plt.show()

出来上がりがこちら。

はい、サクッとできましたね。

ちなみに、pandasのplotメソッドを使わない場合は棒グラフの各色の部分の左端の位置を指定しながら順番に描くため、次のコードになります。

import numpy as np


# 積み上げ棒グラフを作成するための基準位置
left = np.zeros(len(df_normalized))

fig = plt.figure(facecolor="w")
ax = fig.add_subplot(111)


# 各カテゴリごとにバーを追加
for column in df_normalized.columns:
    ax.barh(
        df_normalized.index,
        df_normalized[column],
        left=left,
        label=column,
        height=0.5
    )
    left += df_normalized[column]

# タイトルとラベルの設定
ax.set_title("地域別の販売製品割合")
ax.set_ylabel('地域')
ax.set_xlabel('割合')

# 凡例の表示
ax.legend()

plt.show()

明らかに面倒なコードでしたね。pandasがいい感じに調整してくれていた、棒の太さ(横向きなので今回の例では縦幅)なども自分で調整する必要があります。
Pythonでコーディングしてるのであれば大抵の場合は元データはDataFrameになっているでしょうし、もしそうでなくても変換は容易なので、Pandasに頼った方を採用しましょう。

Lightsail(WordPress)の2023年11月時点のディレクトリ構造

前回の記事で書いた通り、このブログが稼働しているサーバーを新しいインスタンスに移行しました。その中で各作業をやっていてディレクトリ構成が昔と変わっているのが目についたので色々メモっておきます。

自分都合で申し訳ないのですが、今の構造自体の話よりも5年前の先代のインスタンスとの差分に着目して記録しています。自分の記事や手元に残したメモと対比するためです。

バージョンの違い

いつのバージョンと比較するのか明確にするために自分の新旧インスタンスの除法を載せておきます。このブログは2019年の1月から運用していますがサーバーを立てたのはその直前の2018年12月です。両インスタンスにSSHでログインして気づいたのですが、そもそもOSから違いますね。UbuntsだったのがDebianになってます。またアプリも「the Bitnami WordPress」が 「the WordPress packaged by Bitnami」に変わったようです。これは色々変更されていても納得です。

旧インスタンス

Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-1128-aws x86_64)
       ___ _ _                   _
      | _ |_) |_ _ _  __ _ _ __ (_)
      | _ \ |  _| ' \/ _` | '  \| |
      |___/_|\__|_|_|\__,_|_|_|_|_|

  *** Welcome to the Bitnami WordPress 4.9.8-0 ***
  *** Documentation:  https://docs.bitnami.com/aws/apps/wordpress/ ***
  ***                 https://docs.bitnami.com/aws/ ***
  *** Bitnami Forums: https://community.bitnami.com/ ***

新インスタンス

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
       ___ _ _                   _
      | _ |_) |_ _ _  __ _ _ __ (_)
      | _ \ |  _| ' \/ _` | '  \| |
      |___/_|\__|_|_|\__,_|_|_|_|_|

  *** Welcome to the WordPress packaged by Bitnami 6.3.1-23        ***
  *** Documentation:  https://docs.bitnami.com/aws/apps/wordpress/ ***
  ***                 https://docs.bitnami.com/aws/                ***
  *** Bitnami Forums: https://github.com/bitnami/vms/              ***

WordPressのインストール先の変更

WordPressの利用のために使っているインスタンスですからこれが一番重要です。

旧インスタンスでは、Homeディレクトリにappsってディレクトリがあってその下にwordpressもありました。その配下にある、
/home/bitnami/apps/wordpress/htdocs
がドキュメントルートです。wp-config.php などのファイルもここにありました。

新環境では ~/apps はなくなり、wordpressは、/opt/bitnami の配下に移動しています。
/opt/bitnami/wordpress
がドキュメントルートで、wp-config.phpなどはこちらにも移動しています。

Google アドセンスのads.txtの置き場所もこちらへ変更です。

他のブログを見てると、/opt/bitnami/apps ってディレクトリに言及してる人もいるので、もしかしたら時代によっては /opt/bitnami の前に、そこが使われた頃もあったのかもしれません。

DB関係のディレクトリパスの変更

そもそもとして、インストールされているDBがMySQLからMariaDBに変わりました。

# 旧インスタンス
$ which mysql
/opt/bitnami/mysql/bin/mysql

# 新インスタンス
$ which mysql
/opt/bitnami/mariadb/bin/mysql

結果設定ファイルのmy.confの場所も、
/opt/bitnami/mysql/my.conf
から
/opt/bitnami/mariadb/conf/my.conf
に変わっています。

このmy.confの中を見ると、datadir って変数でDBのデータの実態ファイルの書くのディレクトリが指定されていますが、
/opt/bitnami/mysql/data
から
/bitnami/mariadb/data
へと変更されています。

/bitnami ってディレクトリ自体も新設されたものですね。

アップロードした画像等の配置場所

実はアップロードした画像なども先ほど書いた、/bitnami 配下に格納されています。データ関連の実態ファイルを一箇所にまとまったイメージでしょうか。

/opt/bitnami/wordpress で ls -la すると、
wp-content から /bitnami/wordpress/wp-content へシンボリックリンクが貼られていることがわかります。

実は、 wp-config.php も実態のファイルはここにあって、ドキュメントルートにあるのはシンボリックリンクです。

なんか、こう見るとads.txtも正しい置き場所はここで、ドキュメントルートにはシンボリックリンクで対応するべきだったのかなという気がしてきます。

apacheのディレクトリ名変更

これはめっちゃ細かい話ですが、/opt/bitnami/apache2 だったのが、2が取れて /opt/bitnami/apache になりました。ご丁寧にシンボリックリンクが貼ってあって旧パスでもたどり着けるようになっています。

$ ls -la | grep apache
drwxr-xr-x 16 root    root    4096 Oct 10 05:24 apache
lrwxrwxrwx  1 root    root       6 Oct 10 05:24 apache2 -> apache

この下に logs があってそこにアクセスログ等があるのは変わらずです。
/opt/bitnami/apache/logs
に access_log / error_log の名前で入ってます。

LightsailのWordPressを新しいインスタンスに移行する手順

更新遅延について

いつも月曜日更新ですが今週は更新が遅れました。といいうのも、このWordpressが稼働しているLightsailのインスタンスがメモリ不足になり、記事の一覧や新規作成ページが開けなくなったからです。
PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 20480 bytes)
みたいなエラーがエラーログに出ていました。

また、前々からPHPのバージョンが古い(7.0.3.1)ことで管理画面で警告が出ておりいつか対応しないといけないという状況でもありました。

LightsailのwordpressはBitnamiというソフトで稼働しているのですが、実はこれはPHPのバージョンアップができません。PHPのバージョンを上げたければインスタンスを作り直さないといけないという罠があります。

以上の二つの理由で、PHPのバージョンを上げないといけないし、メモリも増やさないといけないしとなったので、Wordpressの移行作業を行いました。

やり方はいろいろあると思うのですが、今回は「All-in-One WP Migration」というプラグインを使いました。

失敗した方法と正しい順序

実は一回しくじったのでそれについて先に書きます。

ブログにアクセスできない時間を最小限にするために実は最初、次の手順でやろうとしていました。

  1. 新しいWordpressのインスタンスを立てる。
  2. 新しいインスタンスに記事やメディアのデータを移行する。
  3. IPアドレスやドメインを新しいインスタンスに移す。
  4. https化などの追加作業を行う。

こうすれば、切り替えた瞬間から訪問者の方が記事を読めると思ってやったのですが、All-in-One WP Migration のプラグインが気が利きすぎていて困ったことが起きました。

それは記事中のテキストや、リンク等に含まれるこのブログのURLが、移行先のインスタンスのURLに勝手に書き換えられてしまったことです。要するに、https://analytics-note.xyz/ って文字列があったら全部、 http://{IPアドレス}/ になってしまいました。

そして、IPアドレスやドメインを設定しても置換されたテキストは元に戻りませんでした。
これを全部治すのは面倒だったので、そのインスタンスは破棄してやり直しました。記事を読めない時間が発生するのは諦めて、次の手順で行い、今のところ正常に動いているようです。

  1. 新しいWordpressのインスタンスを立てる。
  2. IPアドレスやドメインを新しいインスタンスに移す。
  3. https化などの追加作業を行う。
  4. 新しいインスタンスに記事やメディアのデータを移行する。
  5. 残作業の実施。

それでは、細かく手順を説明していきますね。

旧インスタンス側の作業

データのエクスポート

移行するためにデータをローカルに取り出します。先述の通り「All-in-One WP Migration」ってプラグインを使いました。プラグインの一覧から有効化すると左部メニューに登場しますので、エクスポートを選択します。エクスポート先が選べるので、「ファイル」を選ぶとそのままダウンロードが始まります。僕のブログは画像とか少ないのですがそれでも250MBくらいになりました。ちなみに、オプションがいくつかあって僕は次の二つ選びました。これはあまり重要ではないと思います。

  • スパムコメントをエクスポートしない
  • 投稿リビジョンをエクスポートしない

リダイレクト処理の停止

これは、httpやIPアドレスでのアクセスをリダイレクトしてる人だけの作業なので必須ではありません。

旧環境からはドメインを剥奪するので今後はIPアドレス直打ちでしかアクセスできなくなります。僕は普通にドメインでアクセスされなかったらリダイレクトするように設定していたのでそれを戻します。要するに次の記事の逆の手順を行いました。

参考: httpのアクセスをhttpsにリダイレクトする

以上で旧環境での作業は終了です。

新環境作業

インスタンス作成

続いて新環境側の作業ですが、最初にやるのは当然ですが新環境の作成です。以下の記事を参考に新しいインスタンスを立てます。静的IPアドレスは旧環境で使ってたのを使い回すのでインスタンスだけ立てたら大丈夫です。

参考記事: Amazon Lightsail で WordPressサイトを作成する手順

今回はメモリ不足の解消を兼ねていたので、$5の、1GB/2vCPU/40GB SSD を選択しました。

ここでちょっとLightsailの宣伝なのですが、最近$3.5や$5のインスタンスでもvCPUが2個になってます。以前は1個だったので同じ値段で倍のCPUが使える計算になりますね。とはいえ、僕が不足したのはCPUではなくメモリだったので結局以前より高いプランに変えたのですが。これは、特に何か理由がない人も移行するメリットがありそうです。

静的IPの付け替え

失敗した方法のところで説明しましたが、インスタンス作ったらもう早速IPアドレスとそれに紐づいているドメインを新インスタンスに渡します。

インスタンスができたらSSH接続してパスワードを確認します。pemキーはデフォルトでは同じのが使えるのでIPアドレスだけ変えたら繋がります。

GUIで簡単に作業できます。

Lightsail の ネットワーキングにアクセスし、静的IPアドレスを選択。
管理からデタッチし、インスタンスへのアタッチでプルダウンから新しいWordpressを選んでアタッチ、と画面に沿って作業していけば完了です。

これでもう、URL叩いたら新しいWordpress(記事とか何もない初期画面)が開くようになります。

今回、PHPのバージョンアップも目的の一つだったので、SSHで繋いで $php –version してみておきましょう。無事に上がっていますね・

$ php --version
PHP 8.2.11 (cli) (built: Oct  6 2023 09:57:45) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.11, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.11, Copyright (c), by Zend Technologies

https化

Let’s Encryptを使ってhttps化します。

最近は昔と手順が変わっていて、ssh接続して以下のコマンドを叩いてプロンプトに従っていくだけでできるようです。ドメイン名とかメールアドレスを途中で聞かれます。

$ sudo /opt/bitnami/bncert-tool

パスワードの確認と初回ログイン

https化したらデータの移行作業へと入っていきます。wordpressの管理画面にログインしないといけないので初期パスワードを確認します。

SSH接続したホームディレクトリの中に、bitnami_application_password
ってファイルがあり、その中に書かれています。

これを使ってログインします。

データのインポート

All-in-One WP Migrationを使ってインポートするので、最初にやるのはこのプラグインの有効化です。ついでに最新化もしておきましょう。

左メニューにImportが追加されるのでそれを選びます。すると、実はこの時点ではアップロードできるファイルの上限が設定されているのでそれを確認します。おそらくインスタンスサイズによって異なります。

僕の場合は、 Maximum upload file size: 80 MB. と表示されていて、保存していたファイルがそのままだと上げられませんでした。

そこで、PHPの設定を変更してアップロードできるファイルサイズを一時的に増やします。

以下のファイルを開きます。
/opt/bitnami/php/etc/php.ini

その中に、post_max_size と、 upload_max_filesize って設定があるので、それぞれ80Mを512Mとかに書き換えます。(修正前にファイルはバックアップ取りましょう。)

書き換えたら、次のコマンドで再起動します。

$ sudo /opt/bitnami/ctlscript.sh restart

これでアップロードできるファイルサイズが上がるので、エクスポートしていたファイルをアップロードします。

PHP7からPHP8への移行だったので色々警告とか出ましたが仕方ないので全て認めて作業進めます。

この時点で、記事や画像等のメディア、テーマ、小テーマ、プラグイン情報などが移行されほぼほぼ元通り見れるサイトになります。

/opt/bitnami/php/etc/php.ini の中身は元に戻して、もう一回再起動しておきましょう。

再ログインとDB更新

データをインポートすると、wordpressのユーザー情報、要するにパスワードなども復元されるので一回セッションが切れてログアウトします。

次にログインするときは、旧環境のパスワードでログインするので間違えないようにしてください。

ただし、パスワードが戻るのはアプリ(wordpress)のユーザーだけで、MySQLコマンド等で接続するときに使う、DB自体のパスワードは新しい環境のパスワードそのままです。あくまでも書き換わるのはDBの中身だけってことですね。

wordpressの管理画面に再ログインすると、「データベースの更新が必要です」ってメッセージが出るので更新します。

これでほぼ移行完了です。

残作業

残りは細かい残作業やっていきます。

例えばこれとかやる必要があります。
WordPressの一般設定にあるURLをhttpsに変える
LightsailのWordPressにads.txtを設置する
– 不要になったインスタンスの削除

ads.txtの設置場所は
/opt/bitnami/wordpress
に変わってました。

また、旧インスタンスは即消すのではなくしばらく並行しておいておく方がお勧めです。ぼくもしばらくは放置しておこうと思っています。

この後も何か残作業見つかったら順次対応していこうと思います。

Google AdSenseでGDPR同意メッセージを作成する

このブログではGoogle AdSenseを導入していますが、少し前から 管理画面にログインすると、GDPR同意メッセージを作成するよう促すポップアップが表示されるようになっていました。表示されるのはこれです。

かなり面倒だな、もう欧州からのアクセスをブロックしたい、くらいに思って放置していたのですがようやく重い腰を上げて対応したのでそのメモを残しておきます。

どうやら上のポップアップから指示に従って進めていけば設定は完了するようです。念のためドキュメントを見ておきたいという場合は、以下のページが参考になります。
GDPR 同意メッセージについて – Google AdSense ヘルプ
GDPR メッセージを作成する – Google AdSense ヘルプ

自分はアドセンスを設定しているサイトがこのブログだけなので、上のポップアップから完了させてしまいました。

手順

最初にGDPR同意メッセージ作成の同意方法を選択します。上の画面の3つの選択肢(3番目は作成しない、なので実質選択肢は2個)から一つ選ぶのですが、僕は一番上の Google 認定のCMPを使用する方法を選択しました。(細かい違いは理解できてないのですが、おそらく一番上が推奨だと思ったので。)
これで確認を押した時点でポップアップは消えます。

すると、アドセンス上部の赤い枠の注意メッセージで以下の文言が表示されます。

2024 年 1 月 16 日より、欧州経済領域(EEA)または英国(UK)のユーザーに広告を配信するすべてのパブリッシャー様は、Google の認定を受けた同意管理プラットフォーム(CMP)をご使用いただくことが必要となります。欧州経済領域と英国で広告を配信する際には、Google 独自の同意管理ソリューションを含む Google 認定の CMP をご利用いただけます。Google の同意管理ソリューションに関心をお持ちの場合は、まず GDPR メッセージを設定してください。

GDPRメッセージを作成、って文がその下にあるのでそこを押します。

すると、次の4ステップでメッセージが作成できるという説明が出ます。

  1. サイトにプライバシー ポリシーの URL を追加する
  2. 含める同意オプションを選択します
  3. GDPR アカウント設定を確認します
  4. GDPR メッセージを公開します

「使ってみる」を押して進めます。

見慣れない画面に行くので、右側の画面からサイトの選択をします。(自分がGoogle Adsenseを設定しているサイトの一覧がみれると思います。僕はこのブログだけです。)

この時、プライバシーポリシーのURLも設定する必要があります。まだない人はこの機会に作りましょう。

言語は日本語にしました。

「同意しない」や「閉じる」のオプションはオフとしました。

この辺りのオプションはどう選択したらどのようにユーザーに表示されるのかプレビューがー随時見れるので確認しながらいじりましょう。

「スタイル」を選ぶと他にも細かな調整ができます。僕はこのブログにロゴを持ってないので、ヘッダーのロゴをオフにしました(デフォルトがオンだったので)。

ここまで設定したら右上の「公開」ボタンを押します。

今後、メッセージを修正したい場合は、
プライバシーとメッセージから GDPRを選ぶことで先ほどの設定画面に戻れるようです。

テスト方法

設定した内容をテストする方法も用意されています。

参考: プライバシーとメッセージについて – Google アド マネージャー ヘルプ

上記のヘルプページ内の、「サイトのメッセージをテストする手順とパラメータ」という折りたたみコンテンツの中にパラメーターがいくつか紹介されています。

それによると、 自分のブログのURLの最後に、?fc=alwaysshow をつけてアクセスすると、地域を考慮せずにメッセージが表示されるようです。

僕は無事に作成したGDPRメッセージが表示されることを確認できました。