mecobalamin’s diary

人間万事塞翁が馬

サイトの検索結果をスクレイピングする

まとめサイトの検索結果がある
例えば病院を探すサイト、病院なびを使う

東京都豊島区で探すと938件表示される
https://byoinnavi.jp/tokyo/toshimaku
f:id:mecobalamin:20211117155022p:plain

この検索結果から住所や電話番号を抜き出してテキストにしたい

1
医療法人 梅華会東長崎駅前内科クリニック
東京都豊島区長崎4-7-11 マスターズ東長崎1階[地図]
03-5926-9664
休診日:火、日、祝
---
2
南大塚耳鼻咽喉科クリニック
東京都豊島区南大塚2-42-6 信友大塚ビル5階[地図]
03-6912-0187
休診日:日、祝
---


PythonのBeautifulSoupを使う
以前appleのサイトをスクレイピングするスクリプトを書いた
mecobalamin.hatenablog.com

やることは

  1. 検索結果のソースコードの分析
  2. 分析を素にPythonのコードを書く


1. 検索結果のソースコードの分析

必要な情報がどのように記述されているか確認する
google Chromeでは
[右クリック]で[ソースを表示]をクリックする

病院なびでは一つの病院の情報は

<div class='corp_header'>
...
</div>

のタグの間に書かれている
病院名、住所、電話番号、休診日はそれぞれ次のタグで書かれている

<a class='corp-title__name'>...<a>
<div class='corp_address'>...<div>
<div class='corp_tel'>...<div>
<div class='clinic_hour_holiday'>...<div>

このとき

<div class='corp_header'>

<a class='corp-title__name'>
<div class='corp_address'>
<div class='corp_tel'>
<div class='clinic_hour_holiday'>

入れ子になっていて親と子の関係であり、
子供同士は兄弟である

スクレイピングの手順としては
まずBeautifulSoupのobjectから親のcorp_headerを抜き出す
次に子供のcorp-title__nameを抜き出す
それからcorp-title__nameと兄弟関係にある
corp_address、corp_tel、clinic_hour_holidayを抜き出す
という操作をする


2. 分析を元にPythonのコードを書く

BeautifulSoupのobjectをbsObjとするとき
親である'corp_header'を抜き出す

bsObj.find_all('div', {'class':'corp_header'})

次に子に当たる
'corp-title__name'とその兄弟を抜き出すには
find()とfind_next_sibling()を使う

item_title = i.find('a', {'class':'corp-title__name'}).get_text(strip = True)

item_contents = i.find_next_sibling('div', {'class':'corp_contents'})
item_address = item_contents.find('div', {'class':'corp_address'}).get_text(strip = True)
item_tel = item_contents.find('div', {'class':'corp_tel'}).get_text(strip = True)
item_holiday = item_contents.find('div', {'class':'clinic_hour_holiday'})


3. コードのまとめ

コードをまとめると次のようになる

# -*- coding: utf-8 -*-

import urllib
from urllib.request import urlopen
from bs4 import BeautifulSoup
import datetime

today = datetime.date.today()
todaydetail = datetime.datetime.today()

print('----------------------------------')
print("Byoinnavi.jp URL")
print(todaydetail)
print('----------------------------------')

urls = ['https://byoinnavi.jp/tokyo/toshimaku', 'https://byoinnavi.jp/tokyo/toshimaku?p=2', 'https://byoinnavi.jp/tokyo/toshimaku?p=3']

n = 1
for url in urls:
    headers = {"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0"}
    request = urllib.request.Request(url, headers = headers)
    html = urlopen(request).read()

    bsObj = BeautifulSoup(html, 'html.parser')
    bsObj_div = bsObj.find_all('div', {'class':'corp_header'})

    for i in bsObj_div:
        print(n)
        item_title = i.find('a', {'class':'corp-title__name'}).get_text(strip = True)

        item_contents = i.find_next_sibling('div', {'class':'corp_contents'})
        item_address = item_contents.find('div', {'class':'corp_address'}).get_text(strip = True)
        item_tel = item_contents.find('div', {'class':'corp_tel'}).get_text(strip = True)
        item_holiday = item_contents.find('div', {'class':'clinic_hour_holiday'})
        if item_holiday is None:
            item_holiday = ''
        else:
            item_holiday = item_holiday.get_text(strip = True)

        print(item_title)
        print(item_address)
        print(item_tel)
        print(item_holiday)

        print("---")
        n += 1


以下のサイトを参考にした

403を避けるためにブラウザを偽装している
pythonのurllib.request.Requestで403エラー時の対応方法【ユーザエージェントを偽装する】 | エンジニアステップ
NoneTypeだった場合の処理の仕方
[python]変数がNoneTypeであるかを判定する | akamist blog
Siblingの使い方
BeautifulSoup4で親子、兄弟、前後要素の検索方法 | せなブログ

iOSのショートカットでJSONファイルを使う

iOSのショートカットアプリでJSONファイルを使えたのでメモ

目的はショートカットアプリで
ポケモンの攻撃と防御の相性を表示すること
相性の組み合わせをJSONファイルに記録した
どっかのサイトを見ながら入力したので
間違っているところもあるかも

ここから本題

jsonとは
JavaScript Object Notation - Wikipedia
JSONとは| Oracle 日本

大きくやることは2つ

  1. JSONファイルの作成
  2. ショートカットアプリの編集

作成したJSONファイルはpokemon.jsonとして
iCloud以下の/Shortcuts/pokemon/に保存した
ファイルの内容はこの記事の最後に掲載した

ショートカットアプリでやることは

  1. JSONファイルの読み込み
  2. ファイルから辞書を読み込み
  3. 辞書から選択肢を作成
  4. 選択した項目の値を取得
  5. 値からテキストを作成して表示する

ポイントは変数をマジック変数で取得すること
他の言語の感覚だと変数を用意して
代入して使用するが
マジック変数を使うと変数を定義しなくても
任意の場所に任意の変数を読み込める

これは視覚的にプログラミングできて便利

マジック変数は赤丸で
囲んだボタンを使って取得する
f:id:mecobalamin:20210613061209j:plain

実際の作業はここから
ショートカットアプリの編集
1. JSONファイルの読み込み
f:id:mecobalamin:20210613055111j:plain
JSONファイルのパスを設定する
サービスをiCloud
ファイルパスを/Shortcuts/pokemon/
ファイル名をpokemon.json
とした

2. ファイルから辞書を読み込み
f:id:mecobalamin:20210613055125j:plain
アクションの検索から
"入力から辞書を取得"を選択
入力が"辞書"以外なら消去して
マジック変数を選択、
1.で読み込んだファイルを指定する

3. 辞書から選択肢を作成
アクションの検索から
"リストから選択"を選ぶ
入力が"辞書"以外なら消去して
マジック変数を使って
2.で取得した辞書を指定する

4. 選択した項目の値を取得
f:id:mecobalamin:20210613055148j:plain
アクションの検索から
"辞書の値を取得"を選ぶ
入力はマジック変数を使って
3.で"辞書の値"を選び
キーに"こうかばつぐん"を入力する
同様に"こうかいまひとつ"、
"こうかなし"を入力したアクションも作成する

5. 値からテキストを作成して表示する
f:id:mecobalamin:20210613061209j:plain
出力するテキストも
マジック変数を使って
4.で取得した辞書の値を選択する

実行するとJSONをもとに作られた
選択が表示される
f:id:mecobalamin:20210628112538p:plain

技のタイプを選ぶと
技の相性のリストが表示される
f:id:mecobalamin:20210628112648p:plain



アクションは以下のように使った
f:id:mecobalamin:20210628105215p:plain
f:id:mecobalamin:20210628105239p:plain
f:id:mecobalamin:20210628105255p:plain
f:id:mecobalamin:20210628105322p:plain
f:id:mecobalamin:20210628105334p:plain
f:id:mecobalamin:20210628105354p:plain
f:id:mecobalamin:20210628105408p:plain

使用したjsonファイルは以下の通り
pokemon.json

{
  "ノーマル": {
    "攻撃": {
      "こうかばつぐん":["なし"],
      "こうかいまひとつ":["いわ", "はがね"],
      "こうかなし":["ゴースト"]
    },
    "防御": {
      "こうかばつぐん":["かくとう"],
      "こうかいまひとつ":["なし"],
      "こうかなし":["ゴースト"]
    }
  },
  "ほのお": {
    "攻撃": {
      "こうかばつぐん":["くさ", "こおり", "むし", "はがね"],
      "こうかいまひとつ":["ほのお", "みず", "いわ", "ドラゴン"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["みず", "じめん", "いわ"],
      "こうかいまひとつ":["ほのお", "くさ", "こおり", "むし", "はがね", "フェアリー"],
      "こうかなし":["なし"]
    }
  },
  "みず": {
    "攻撃": {
      "こうかばつぐん":["ほのお", "じめん", "いわ"],
      "こうかいまひとつ":["みず", "くさ", "ドラゴン"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["でんき", "くさ"],
      "こうかいまひとつ":["ほのお", "みず", "こおり", "はがね"],
      "こうかなし":["なし"]
    }
  },
  "くさ": {
    "攻撃": {
      "こうかばつぐん":["みず", "じめん", "いわ"],
      "こうかいまひとつ":["ほのお", "くさ", "どく", "ひこう", "むし", "ドラゴン", "はがね"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["ほのお", "こおり", "どく", "ひこう", "むし"],
      "こうかいまひとつ":["みず", "でんき", "くさ", "じめん"],
      "こうかなし":["なし"]
    }
  },
  "でんき": {
    "攻撃": {
      "こうかばつぐん":["みず", "ひこう"],
      "こうかいまひとつ":["でんき", "くさ", "ドラゴン"],
      "こうかなし":["じめん"]
    },
    "防御": {
      "こうかばつぐん":["じめん"],
      "こうかいまひとつ":["でんき", "ひこう", "はがね"],
      "こうかなし":["なし"]
    }
  },
  "こおり": {
    "攻撃": {
      "こうかばつぐん":["くさ", "じめん", "ひこう", "ドラゴン"],
      "こうかいまひとつ":["ほのお", "みず", "こおり", "はがね"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["ほのお", "かくとう", "いわ", "はがね"],
      "こうかいまひとつ":["こおり"],
      "こうかなし":["なし"]
    }
  },
  "かくとう": {
    "攻撃": {
      "こうかばつぐん":["ノーマル", "こおり", "いわ", "あく", "はがね"],
      "こうかいまひとつ":["どく", "ひこう", "エスパー", "むし", "フェアリー"],
      "こうかなし":["ゴースト"]
    },
    "防御": {
      "こうかばつぐん":["ひこう", "エスパー", "フェアリー"],
      "こうかいまひとつ":["むし", "いわ", "あく"],
      "こうかなし":["ゴースト"]
    }
  },
  "どく": {
    "攻撃": {
      "こうかばつぐん":["くさ", "フェアリー"],
      "こうかいまひとつ":["どく", "じめん", "いわ", "ゴースト"],
      "こうかなし":["はがね"]
    },
    "防御": {
      "こうかばつぐん":["じめん", "エスパー"],
      "こうかいまひとつ":["くさ", "かくとう", "どく", "むし", "フェアリー"],
      "こうかなし":["なし"]
    }
  },
  "じめん": {
    "攻撃": {
      "こうかばつぐん":["ほのお", "でんき", "どく", "いわ", "はがね"],
      "こうかいまひとつ":["くさ", "むし"],
      "こうかなし":["ひこう"]
    },
    "防御": {
      "こうかばつぐん":["みず", "じめん", "こおり"],
      "こうかいまひとつ":["どく", "いわ"],
      "こうかなし":["でんき"]
    }
  },
  "ひこう": {
    "攻撃": {
      "こうかばつぐん":["Glound", "かくとう", "むし"],
      "こうかいまひとつ":["でんき", "いわ", "はがね"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["でんき", "こおり", "いわ"],
      "こうかいまひとつ":["くさ", "かくとう", "むし"],
      "こうかなし":["じめん"]
    }
  },
  "エスパー": {
    "攻撃": {
      "こうかばつぐん":["かくとう", "どく"],
      "こうかいまひとつ":["エスパー", "はがね"],
      "こうかなし":["ゴースト"]
    },
    "防御": {
      "こうかばつぐん":["むし", "ゴースト", "あく"],
      "こうかいまひとつ":["かくとう", "エスパー"],
      "こうかなし":["なし"]
    }
  },
  "むし": {
    "攻撃": {
      "こうかばつぐん":["くさ", "エスパー", "ゴースト"],
      "こうかいまひとつ":["ほのお", "かくとう", "どく", "ひこう", "ゴースト", "はがね", "フェアリー"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["ほのお", "ひこう", "いわ"],
      "こうかいまひとつ":["くさ", "かくとう", "じめん"],
      "こうかなし":["なし"]
    }
  },
  "いわ": {
    "攻撃": {
      "こうかばつぐん":["ほのお", "こおり", "ひこう", "むし"],
      "こうかいまひとつ":["かくとう", "じめん", "はがね"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["みず", "くさ", "かくとう", "じめん", "はがね"],
      "こうかいまひとつ":["ノーマル", "ほのお", "どく", "ひこう"],
      "こうかなし":["なし"]
    }
  },
  "ゴースト": {
    "攻撃": {
      "こうかばつぐん":["エスパー", "ゴースト"],
      "こうかいまひとつ":["あく"],
      "こうかなし":["ノーマル"]
    },
    "防御": {
      "こうかばつぐん":["ゴースト", "あく"],
      "こうかいまひとつ":["どく", "むし"],
      "こうかなし":["ノーマル", "かくとう"]
    }
  },
  "ドラゴン": {
    "攻撃": {
      "こうかばつぐん":["ドラゴン"],
      "こうかいまひとつ":["はがね"],
      "こうかなし":["フェアリー"]
    },
    "防御": {
      "こうかばつぐん":["こおり", "ドラゴン", "フェアリー"],
      "こうかいまひとつ":["ほのお", "みず", "でんき", "くさ"],
      "こうかなし":["なし"]
    }
  },
  "あく": {
    "攻撃": {
      "こうかばつぐん":["エスパー", "ゴースト"],
      "こうかいまひとつ":["かくとう", "あく", "フェアリー"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["かくとう", "むし", "フェアリー"],
      "こうかいまひとつ":["ゴースト", "あく"],
      "こうかなし":["エスパー"]
    }
  },
  "はがね": {
    "攻撃": {
      "こうかばつぐん":["こおり", "いわ", "フェアリー"],
      "こうかいまひとつ":["ほのお", "みず", "でんき", "はがね"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["ほのお", "かくとう", "じめん"],
      "こうかいまひとつ":["ノーマル", "くさ", "こおり", "ひこう", "エスパー", "むし", "いわ", "ドラゴン", "はがね", "フェアリー"],
      "こうかなし":["どく"]
    }
  },
  "フェアリー": {
    "攻撃": {
      "こうかばつぐん":["かくとう", "ドラゴン", "あく"],
      "こうかいまひとつ":["ほのお", "どく", "はがね"],
      "こうかなし":["なし"]
    },
    "防御": {
      "こうかばつぐん":["どく", "はがね"],
      "こうかいまひとつ":["かくとう", "むし", "あく"],
      "こうかなし":["ドラゴン"]
    }
  }
}

Pythonで時系列グラフに水平線と矢印を入れる

以前Pythonで時系列グラフを作成した
mecobalamin.hatenablog.com

このグラフに矢印と水平線を加える
つまりこのグラフの任意の場所に矢印と水平線を加えて
f:id:mecobalamin:20210301160250p:plain
このようにする
f:id:mecobalamin:20210301160303p:plain

元のデータはCSVで500行ぐらいある
その一部を抜粋

Date, a, b, c, d
2020/11/24 16:42:20, 143, 101, 63,
2020/11/24 17:27:19, 137, 101, 65,
2020/11/25 06:31:48, 134, 90, 65,
2020/11/25 11:21:57, 142, 109, 67,
2020/11/25 14:06:42, 131, 94, 74,
2020/11/25 18:54:18, 120, 91, 69,
2020/11/25 20:42:53, 114, 78, 72,
2020/11/26 09:09:53, 113, 84, 76,
2020/11/26 09:17:28, 125, 70, 70, 1021.67
2020/11/26 16:12:06, 126, 89, 65, 1019.64
2020/11/27 06:37:47, 117, 81, 69, 1020.66
2020/11/27 13:31:37, 123, 88, 98, 1019.98
2020/11/27 14:22:03, 126, 86, 89, 1019.64
2020/11/27 17:58:24, 120, 86, 84, 1020.32
2020/11/27 19:35:42, 116, 81, 81, 1021
2020/11/27 21:35:24, 120, 86, 78, 1021.33
2020/11/28 08:30:20, 133, 98, 69, 1022.69
2020/11/28 12:59:11, 127, 82, 72, 1022.01
2020/11/28 20:59:20, 119, 86, 67, 1022.69
2020/11/28 22:06:02, 112, 75, 66, 1023.03

dfという変数に格納されている

今回のポイントは

  1. axesオブジェクトを返す
  2. 矢印をいれる関数を作成する
  3. 水平線はplot関数を使う

1. axesオブジェクトを返す
グラフを作成する関数を書いてある
関数の内部に矢印を入れる命令を書いてもいいのだが
汎用性をもたせたいのでグラフと矢印の描画を分けたい
グラフを以下のようにして描く

fig = plt.figure(figsize = (12.0, 9.0))
ax1 = fig.subplots()

グラフを書いたaxesオブジェクトをretrunで戻す

return ax1

matplotlibについてはこちらを参考にした
matplotlibの描画の基本 - figやらaxesやらがよくわからなくなった人向け - Qiita

2. 矢印をいれる関数を作成する
グラフのデータdfはgraph_dataに入っている
関数の引数として
矢印を書き込むaxesオブジェクト、
矢印の色、矢印の長さ
を渡す
矢印は上下方向にしか引かないので
矢印の長さで向きを示す
正の値が下向き、負の値が上向きになる

日付と矢印を入れるy軸の値で
インスタンスを生成し、
矢印を書き込む関数(メソッド)を呼び出す

CreateGraphs({'2020-12-03 10:00:00':'15', '2020-12-11 10:00:00':'15', '2020-12-18 10:00:00':'15', '2021-01-08 10:00:00':'15', '2021-01-15 10:00:00':'15', '2021-01-22 10:00:00':'15', '2021-02-12 10:00:00':'15', '2021-02-19 10:00:00':'15'}).add_arrows(ax, '#ff4500', -10)

この場合は8個の矢印を描く

矢印を書き込む関数は以下の通り

def add_arrows(self, ax, arrow_color, arrow_direction):
    values_infection = self.graph_data

    date_infection = values_infection.keys()
    for key in date_infection:
        ax.annotate("", xy = (pd.to_datetime(key), int(values_infection[key])),
                xytext = (pd.to_datetime(key), int(values_infection[key]) + arrow_direction),
                arrowprops = dict(shrink = 0, width = 1, headwidth = 8,
                                headlength = 10, connectionstyle = 'arc3',
                                facecolor = arrow_color, edgecolor = arrow_color))

iOSのPythonista3ではheadlengthに関してエラーが出る
環境によってはarrowpropsを以下のように書き換える必要があるかも

arrowprops = dict(arrowstyle = '->, head_width = 0.2, head_length = 0.5', connectionstyle = 'arc3', facecolor = arrow_color, edgecolor = arrow_color)


3. 水平線はplot関数を使う
水平線を入れるメソッドhlinesもあるが
グラフの値を重ねられてしまう

ax.hlines([128], min(df.index), max(df.index), "blue", linestyles = 'dashed')

コードの最後に書いてもグラフに隠れる

そこでplot()を使って水平線を描く

ax.plot([min(df.index), max(df.index)],[128, 128], "blue", linestyle='dashed')

コードをまとめるとこの様になる

# -*- coding: utf-8 -*-

import os, sys, datetime
from pandas import Series, DataFrame
import pandas as pd

import numpy as np

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns

class ReadFiles:
    def __init__(self):
        pass

    def get_current_path(self):
        current_path = os.getcwd()
        current_path = os.chdir(os.path.dirname(os.path.abspath(__file__)))
        current_path = os.getcwd()

        return(current_path)

    def create_directories(self, path_to_dir):
        try:
            os.makedirs(path_to_dir)
        except FileExistsError:
            pass

    def read_files(self, path_to_file):
        print('open a csv file.')
        try:
            df = pd.read_csv(path_to_file)
        except Exception as e:
            print('cannot open file: ', path_to_file)
            print(str(e))
            sys.exit(1)
        else:
            print('Success!')

        return(df)

class CreateGraphs:
    def __init__(self, graph_data):
        self.graph_data = graph_data

    def create_line_graph(self):
        print('Create line plot')

        df = self.graph_data

        fig = plt.figure(figsize = (12.0, 9.0))
        ax1 = fig.subplots()
        plt.subplots_adjust(left=0.125, right=0.9, bottom=0.3, top=0.9)

        plt.ylim(0, 200)
        plt.ylabel('a/b')
        ax1.plot(df.index, df.iloc[:, 0], linestyle = '-', marker = 'o', color = '#ff7f50', label = df.columns[0])
        ax1.plot(df.index, df.iloc[:, 1], linestyle = ':', marker = '+', color = '#00ff7f', label = df.columns[1])

        ax2 = ax1.twinx()
        plt.ylim(40, 300)
        plt.ylabel('c')
        ax2.plot(df.index, df.iloc[:, 2], linestyle = '--', marker = '.', color = '#00bfff', label = df.columns[2])

        locator = mdates.AutoDateLocator()
        formatter = mdates.ConciseDateFormatter(locator)
        ax1.xaxis.set_major_locator(locator)
        ax1.xaxis.set_major_formatter(formatter)

        handles1, labels1 = ax1.get_legend_handles_labels()
        handles2, labels2 = ax2.get_legend_handles_labels()
        ax1.legend(handles1 + handles2, labels1 + labels2, loc = 'upper right')

        return ax1

    def add_arrows(self, ax, arrow_color, arrow_direction):
        values_infection = self.graph_data

        date_infection = values_infection.keys()
        for key in date_infection:
            ax.annotate("", xy = (pd.to_datetime(key), int(values_infection[key])),
                xytext = (pd.to_datetime(key), int(values_infection[key]) + arrow_direction),
                arrowprops = dict(shrink = 0, width = 1, headwidth = 8,
                                headlength = 10, connectionstyle = 'arc3',
                                facecolor = arrow_color, edgecolor = arrow_color))

if __name__ == '__main__':

    date_today = datetime.date.today()
    day_save_file = str(date_today.year) + str(date_today.month).zfill(2) + str(date_today.day).zfill(2)

    input_file_name = 'Python_Sample.csv'
    output_dir_name = 'graph'
    output_graph_name = day_save_file + '_graph_sample.png'

    path_input_file = ReadFiles().get_current_path() + '\\' + input_file_name
    path_output_dir = ReadFiles().get_current_path() + '\\' + output_dir_name + '\\'
    print(path_input_file)

    ReadFiles().create_directories(path_output_dir)
    df = ReadFiles().read_files(path_input_file)
    print(df.iloc[-1])
    print(df.mean())

    df['Date'] = pd.to_datetime(df['Date'])
    df = df.set_index('Date')
    ax = CreateGraphs(df).create_line_graph()

    CreateGraphs({'2020-12-03 10:00:00':'15', '2020-12-11 10:00:00':'15', '2020-12-18 10:00:00':'15', '2021-01-08 10:00:00':'15', '2021-01-15 10:00:00':'15', '2021-01-22 10:00:00':'15', '2021-02-12 10:00:00':'15', '2021-02-19 10:00:00':'15'}).add_arrows(ax, '#ff4500', -10)
    CreateGraphs({'2020-12-25 10:00:00':'15', '2021-01-01 10:00:00':'15', '2021-01-29 10:00:00':'15', '2021-02-26 10:00:00':'15'}).add_arrows(ax, '#a9a9a9', -10)
    CreateGraphs({'2021-02-03 10:00:00':'15'}).add_arrows(ax, '#00008b', -10)

    ax.plot([min(df.index), max(df.index)],[128, 128], "blue", linestyle='dashed')

    plt.savefig(path_output_dir + '\\' + output_graph_name)
    plt.close()

    print('Operation completed')

メモ: DockerでNginxを使う

使い方を忘れていたのでメモ

Nginxの起動

$ docker run --rm -v /e/www:/usr/share/nginx/html -d -p 8080:80 --name nginx nginx

起動中のコンテナの確認

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
ea3663501739        nginx               "/docker-entrypoint.…"   27 seconds ago      Up 27 seconds       0.0.0.0:8080->80/tcp   nginx

コンテナの終了

$ docker stop nginx

コンテナイメージのリスト

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              992e3b7be046        4 months ago        133MB
ubuntu              latest              9140108b62dc        4 months ago        72.9MB

Windowsのブラウザからアクセスするには

http://192.168.3.4:8000/

wslのifconfigでwifi0のIPアドレスを確認する

ポートの設定は過去記事を参照する
mecobalamin.hatenablog.com

Pythonを使って人口ピラミッドのグラフを作る

Pythonのseabornを使って以下のような人口ピラミッドのグラフを作りたい
f:id:mecobalamin:20210214160109p:plain

元のデータ

man woman
90+ 47.0 128.0
80 - 89 174.0 205.0
70 - 79 321.0 257.0
60- 69 562.0 393.0
50 - 59 604.0 442.0
40 - 49 730.0 501.0
30 - 39 670.0 478.0
20 - 29 895.0 717.0
10 - 19 309.0 258.0
0 - 9 123.0 132.0
Undisclosed 2.0 8.0
Missing number NaN NaN

ここのやり方を参考にさせてもらった
Matplotlib - Python3のmatplotlibを使ったヒストグラムの作図|teratail

上記の表はdf_ageという変数に入っているとする
型はpandasのDataFrame

print(df_age)
                  man  woman
90+              47.0  128.0
80 - 89         174.0  205.0
70 - 79         321.0  257.0
60- 69          562.0  393.0
50 - 59         604.0  442.0
40 - 49         730.0  501.0
30 - 39         670.0  478.0
20 - 29         895.0  717.0
10 - 19         309.0  258.0
0 - 9           123.0  132.0
Undisclosed       2.0    8.0
Missing number    NaN    NaN

print(type(df_age))
<class 'pandas.core.frame.DataFrame'>

男女のデータを一つのヒストグラムに描画する
今回キモになっているのは以下の3つ

  1. データの反転
  2. 一つの図に2つのグラフを書き込む
  3. 軸の書き換え

データの反転
参考にしたサイトにも説明されているが
片方のデータを負の値に反転させている

df_age["man"] *= -1

一つの図に2つのグラフを書き込む
seabornのbarplotを使っている
zip関数を使ってman/womanのデータを
うまく選択している

for name, color in zip(age_names, age_colors):
        sns.barplot(x = name, y = df_age.index, 
            data = df_age, color = color, label = name,
            orient = 'h', order = df_age.index, 
            ax = ax)

zip関数
Python, zip関数の使い方: 複数のリストの要素をまとめて取得 | note.nkmk.me

軸の書き換え
横軸が負->正となるので
グラフの中心を0として
両側に正の値になるように
ラベルを書き換える

ax.set_xticklabels(['1000', '750', '500', '250', '0', '250', '500', '750', '1000'])


コードを以下にまとめる

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

sns.set(style = 'whitegrid')
fig, ax = plt.subplots(figsize = (7, 5))
plt.subplots_adjust(left = 0.2, right = 0.85, bottom = 0.05, top = 1.0)

df_age["man"] *= -1

age_colors = ["#4169e1", "#ff1493"]
age_names = df_age.columns

for name, color in zip(age_names, age_colors):
    sns.barplot(x = name, y = df_age.index, data = df_age, color = color, label = name,
    orient = 'h', order = df_age.index, ax = ax)

ax.set_xlabel("")
ax.set_ylabel("age", fontsize = 12)
ax.set_xlim(-1000, 1000)
ax.set_xticklabels(['1000', '750', '500', '250', '0', '250', '500', '750', '1000'])
ax.legend(loc = 'lower left')

plt.savefig('graph.png')
plt.close()

TimedeltaIndexをastypeで変換する

日付の差分をとってヒストグラムを作る

元のデータはこれ

a b
1 2021年1月15日 2021年1月16日
2 2021年1月14日 2021年1月16日
3 2021年1月14日 2021年1月16日
4 2021年1月15日 2021年1月16日
5 2021年1月15日 2021年1月16日
6 2021年1月13日 2021年1月16日

以下続く

aとbの差分を取ると1行目は1日、6行目は3日である

上記のデータがデータフレームdfに入っているとするとき
差分をとって頻度を数えるには以下のようにする

delta = df.iloc[:, 1] -df.iloc[:, 0]
delta = delta.valu_counts().sort_index()

このときdeltaは以下のようになっていて

1 days      798
2 days     1324
3 days      852
4 days      654

型は

print(type(delta))
<class 'pandas.core.series.Series'>

となっている

このままヒストグラムにつかえそうだが
以下のようにするとエラーになる

fig = plt.figure(figsize = (12.0, 9.0))
ax = fig.subplots()
ax.bar(delta.index, delta)

エラーメッセージは以下の通り

TypeError: the dtypes of parameters x (timedelta64[ns]) and width (float64) are incompatible

deltaのindexの型を調べるとTimedeltaIndexになっている

print(type(delta.index))
<class 'pandas.core.indexes.timedeltas.TimedeltaIndex'>

この場合、indexの型を変えるとうまくいった

ax.bar(delta.index.astype('str'), delta)

Pythonでtweetを収集する

Pythonを使ってtweetを収集する

twintを使う
twint · PyPI

インストール

$ pip3 install twint

twintはコマンドラインpythonスクリプトの両方から使える

例えば@usernameのツイートから
Donald Trunpに関係するツイートを収集して
json形式のファイルに保存する

コマンドラインからは以下のようにする

$ twint -u username -s "Donald Trump"  -o file.json --json

pythonスクリプトの場合はこんな感じ

import twint, json

path_json = 'c:\\path\\to\\json\\file.json'

with open(path_json, 'w'):
    pass

c = twint.Config()
c.Username = "username"
c.Search = "Donald Trump"
c.Hide_output = True
c.Store_json = True
c.Output = path_json

twint.run.Search(c)

json_dict = []
with open(path_json, 'r', encoding = 'utf-8') as f:
    for line in f:
        json_dict.append(json.loads(line))

あとは出力されたjsonファイルを
煮るなり焼くなり好きに処理する

実行時に
>
CRITICAL:root:twint.get:User:'NoneType' object is not subscriptable + Unicode<
といったエラーが出る場合は
twintをインストールし直す
CRITICAL:root:twint.get:User:'NoneType' object is not subscriptable + Unicode issues · Issue #384 · twintproject/twint · GitHub

pip3 install --upgrade -e git+https://github.com/twintproject/twint.git@origin/master#egg=twint

あと、Jsonファイルはcp932でエンコードされているが
pythonで読み込むとエラーになる
それでencodingにutf-8を指定している

windowsだとこういう方法もある
【Windows】PythonでCP932(Shift-JIS)エンコード以外のファイルを開くとエラーになる問題がとりあえずの解決に至った件 - Qiita