20210726のPythonに関する記事は30件です。

Djangoの旅 ~Part8~ モデルの更新編

目的 新しいカラムを追加した時のモデルの更新 流れ 1・モデルに新しいカラム追加 2・マイグレーションファイル作成 3・マイグレート コード解説 APP/testapp/models.py from django.db import models from django.db.models.fields import CharField class LikeFood(models.Model): content = CharField(max_length = 200) color = CharField(max_length = 200,null=True)#追加 def __self__(self): return self.content 1・モデルに新しいカラム追加 今回はcolorカラムを追加 ここでは元から更新以前のデータで生じる欠損値の扱い方が重要になってくる!!! null=Trueを記述することにより空白のデータを欠損値nullとして認識してくれる。 null=Trueを記述しないとマイグレーションファイル作成時、以下のエラー文で出力されるが慌てず,2を選択し null=Trueを追加しよう!!! $ python manage.py makemigrations testapp #出力結果 You are trying to add a non-nullable field 'color' to likefood without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option: 2 $ python manage.py makemigrations testapp 2・マイグレーションファイル作成 $ python manage.py migrate 3・マイグレート
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoの旅 ~Part8~ Django Shell編

目標 Django Shellの理解 Django Shellとは Djangoアプリの環境を有効にしたまま、コマンドでDBモデルを操作できる機能のこと コード解説 $ python manage.py shell #出力結果 Python 3.6.3 (default, Sep 21 2020, 20:38:53) [GCC Apple LLVM 12.0.0 (clang-1200.0.32.2)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> 1・まずはDjnago シェル起動 python manage.py shellを打ち込む 出力結果のようになればOK Djangoシェルのメリットの1つはDB操作をpythonコードで実行できること >>> from testapp.models import LikeFood as LF 2・モデルの読み込み 現在いる階層はmanage.pyがある階層と同じなのでtestapp.models のようにtestapp.が必要 >>> LF.objects.all() #出力結果 <QuerySet [<LikeFood: LikeFood object (1)>, <LikeFood: LikeFood object (2)>, <LikeFood: LikeFood object (3)>]> 3・モデルの全データ取得 >>> food = LF.objects.get(id = 1) >>> food.content 4・指定idのデータを抽出し、contentカラムを出力 今回モデルはcontentカラムだったがここは任意のカラムに変更 >>> food = LF(content = 'バナナ') >>> food.content 'バナナ' >>> food.save() >>> LF.objects.all() #出力結果 <QuerySet [<LikeFood: LikeFood object (1)>, <LikeFood: LikeFood object (2)>, <LikeFood: LikeFood object (3)>, <LikeFood: LikeFood object (4)>]> 5・データ追加・保存 >>> food = LF.objects.get(pk=1) >>> food.content 'みかん' >>> food.content = "パイナップル" >>> food.content 'パイナップル' >>> food.save() 6・データ修正 >>> LF.objects.all() <QuerySet [<LikeFood: LikeFood object (2)>, <LikeFood: LikeFood object (3)>, <LikeFood: LikeFood object (7)>, <LikeFood: LikeFood object (1)>]> >>> food = LF.objects.get(pk=7) >>> food.content 'すいか' >>> food.delete() (1, {'testapp.LikeFood': 1}) >>> LF.objects.all() <QuerySet [<LikeFood: LikeFood object (2)>, <LikeFood: LikeFood object (3)>, <LikeFood: LikeFood object (1)>]> 7・削除 deleteの時はsave()しなくていい????
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで行列の計算

必要なライブラリ import numpy as np 行列(matrix)の作成 X = np.array([[1,2],[3,4]]) print(X) # [[1 2] # [3 4]] 行列の転置(transpose) X = np.array([[1,2],[3,4]]) Xt = X.T print(Xt) # [[1 3] # [2 4]] 逆行列(inverse matrix)の作成 X = np.array([[1,2],[3,4]]) X_inv = np.linalg.inv(X) print(X_inv) # [[-2. 1. ] # [ 1.5 -0.5]] 行列の積 X = np.array([[1,2],[3,4]]) X_inv = np.linalg.inv(X) XX_inv = np.dot(X,X_inv) print(XX_inv) # [[1.0000000e+00 0.0000000e+00] # [8.8817842e-16 1.0000000e+00]] # 単位行列(identity matrix) 行列の和 X = np.array([[1,2],[3,4]]) print(X + X) # [[2 4] # [6 8]] 行列の差 X = np.array([[1,2],[3,4]]) Xt = X.T print(X - Xt) # [[ 0 -1] # [ 1 0]] 行列の各成分の積 X = np.array([[1,2],[3,4]]) print(X * X) # [[ 1 4] # [ 9 16]] 行、列の数 X = np.array([[1,2,3],[4,5,6]]) print(X.shape) # (2,3) row, col = X.shape print(row) print(col) # 2 # 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonのnumpyで行列の計算

必要なライブラリ import numpy as np 行列(matrix)の作成 X = np.array([[1,2],[3,4]]) print(X) # [[1 2] # [3 4]] 行列の転置(transpose) X = np.array([[1,2],[3,4]]) Xt = X.T print(Xt) # [[1 3] # [2 4]] 逆行列(inverse matrix)の作成 X = np.array([[1,2],[3,4]]) X_inv = np.linalg.inv(X) print(X_inv) # [[-2. 1. ] # [ 1.5 -0.5]] 行列の積 X = np.array([[1,2],[3,4]]) X_inv = np.linalg.inv(X) XX_inv = np.dot(X,X_inv) print(XX_inv) # [[1.0000000e+00 0.0000000e+00] # [8.8817842e-16 1.0000000e+00]] # 単位行列(identity matrix) 行列の和 X = np.array([[1,2],[3,4]]) print(X + X) # [[2 4] # [6 8]] 行列の差 X = np.array([[1,2],[3,4]]) Xt = X.T print(X - Xt) # [[ 0 -1] # [ 1 0]] 行列の各成分の積 X = np.array([[1,2],[3,4]]) print(X * X) # [[ 1 4] # [ 9 16]] 行、列の数 X = np.array([[1,2,3],[4,5,6]]) print(X.shape) # (2,3) row, col = X.shape print(row) print(col) # 2 # 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】for文で入れ子のタプルをアンパッキング

タプルをアンパッキング アンパッキングはタプルの各要素を、複数の変数に代入することである。以下の例では2つの要素を2つの変数に対して代入している。 menu="pizza","coffee" main,drink=menu print(main) print(drink) 実行結果 pizza coffee for文を使ってタプルのアンパッキングを行う。 itemsメソッドは辞書のキーと値の対をタプルで返す。このためmenu_itemにはタプルが代入されている。 以下の例はx,yのそれぞれにキーと値をアンパッキングしている。 menu=dict(main="pizza",drink="coffee") menu_item=menu.items() for x,y in menu_item: print(x,y) 実行結果 main pizza drink coffee 入れ子になったタプルをアンパッキング enumerate関数はループのカウントと要素をタプルで返す。以下の例ではenumerate関数の中に、辞書のキーと値をタプルで返すitemsメソッドが入っているため入れ子のタプルをアンパッキングすることになる。この場合は、入れ子のタプルの形式にあわせて、for文の変数にかっこをつける必要がある。(以下の例ではx,yをかっこの中に入れている。) menu=dict(main="pizza",drink="coffee") for i,(x,y) in enumerate(menu.items(),1): print(i,x,y) 実行結果 1 main pizza 2 drink coffee
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでExcelファイルを読み込む

使用するExcelファイル 以下の内容でtest.xlsファイルを作成します。 Sheet1: Sheet2: PythonでExcelを読み込む 以下pandasを使用してExcelファイルを読み込みます。(ExcelFileではxlsxファイルは現時点サポートされません。) シート名を取得します。 import pandas as pd xls_data = pd.ExcelFile('test.xls') print(xls_data.sheet_names) 出力: ['Sheet1', 'Sheet2'] 各シートの内容を読み込みます。インデックスもしくはシート名で対象シートを指定します。 df1 = xls_data.parse(0) print(df1.head()) df2 = xls_data.parse('Sheet2') print(df2.head()) 出力: col1 col2 col3 0 1 value1 value1 1 2 value2 value2 2 3 value3 value3 列1 列2 0 1 値1 1 2 値2 2 3 値3 読込みオプション 以下のようにオプションを使用しての読込みも可能です。 # 1行目スキップ、カラム名のリネーム df1 = xls_data.parse(0, skiprows=[0], names=['Index','Value01', 'Value02']) print(df1.head()) # 1列目指定、1行目スキップ、カラム名のリネーム df2 = xls_data.parse('Sheet2', usecols=[0], skiprows=[0], names=['インデックス']) print(df2.head()) 出力: Index Value01 Value02 0 2 value2 value2 1 3 value3 value3 インデックス 0 2 1 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Intellij Ideaに必要なPythonパッケージをインストールする

モジュールがないエラー Intellij IdeaのうえでExcelを読み込むプログラムを組む際、以下のエラーが出ました。 ModuleNotFoundError: No module named 'xlrd' xlrdモジュールがないというとこで、以下の手順で追加できました。 Intellij IdeaにPythonパッケージをインストールする tools -> Manage Python Packagesをクリックします。 +をクリックし、必要なパッケージを検索し、Install Packageをクリックします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ChromeDriverを自動更新するライブラリ

概要 SeleniumとChromeを使用してブラウザ操作の自動化をしていると、Chromeのバージョンアップの度にエラーが発生し、ChromeDriverを更新するということがあります。 今回紹介するwebdriver_managerというライブラリを使用すると、自動でChromeDriverの更新を行ってくれるようになります。 使用方法 1.ライブラリのインストール console pip install webdriver-manager 2.コードの変更 python 変更前 from selenium import webdriver import chromedriver_binary driver = webdriver.Chrome() driver.get('https://google.com') python 変更後 from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(ChromeDriverManager().install()) driver.get('https://google.com') 3.実行 通常通り実行するだけです。 実行時にChromeのバージョンとChromeDriverがサポートするバージョンが異なる場合は、Chromeのバージョンに合うChromeDriverを自動的にダウンロードしてから実行されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoの旅 ~Part7~ モデルのデータ詳細表示編

目標 一覧表示ページから個別ページに移動 URL説明 ~/testapp/ index 一覧表示 ~/testapp/id detail 個別表示 ~/testapp/ admin.site.urls 管理サイト 流れ 1・個人ページに移動するためにurls.pyに記述 2・views.pyで個別ページに値を渡す 3・個別ページ作成 4・index.htmlを変更 5・確認 コード解説 APP/testapp/urls.py from django.urls import path from . import views app_name = 'testapp'#追加 urlpatterns = [ path('',views.index,name = 'index'), path('<int:id>',views.detail,name ='detail')#追加 ] 1・個人ページに移動するためにurls.pyに記述 app_name = 'testapp'・・・変数app_nameでアプリ名を指定すると後々便利(後半出てくる) 'int:id'・・・testapp/1,testapp/2,,,のなどパスの最後が整数の時読みこんでくれるようになる!!! APP/testapp/views.py from django.shortcuts import render, get_object_or_404#追加 from django.http import HttpResponse from .models import LikeFood # Create your views here. def index(request): foods = LikeFood.objects.all() context = { 'foods':foods } return render(request,'testapp/index.html',context) """追加""" def detail(request, id): food = get_object_or_404(LikeFood,pk = id) context = { 'message': 'Show Food ' + str(id), 'food':food, } return render(request,'testapp/detail.html',context) 2・views.pyで個別ページに値を渡す (request,id)でURL情報とidを受け取って ショートカット関数get_object_or_404でモデルからidに一致するデータを取得する APP/testapp/templates/testapp/detail.html <body> <h1>好きな食べ物</h1> <p>{{ message }}</p> <p>{{ food.content }}</p> <p><a href='{% url "testapp:index" %}'>一覧</a></p><!--ここ重要--> </body> 3・個別ページ作成 food.content・・・foodのcontentカラムを表示 '{% url "testapp:index" %}'・・・ testapp:indexでtestappアプリ内のurls.pyのname属性indexのpath()を起動 APP/testapp/templates/testapp/index.html {% for food in foods %} <p> {{ food.content }}, <a href='{% url "testapp:detail" food.id %}'>詳細</a> </p> {% endfor %} 4・index.htmlを変更 {% url %}・・・テンプレートタグ testapp:detail・・・testapp:detailでtestappアプリ内のurls.pyのname属性detailのpath()を起動 food.id・・・foodデータのidも渡す $ python manage.py runserver 5・確認
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jupyter Notebookを一瞬で記事にできて誰でも収益化できるサービス作ってみた

はじめに あらゆるサイエンス領域で使われているJupyter Notebookですが、誰でもシェアできるサービスはGithub / Colab・Kaggleしかありませんでした。しかも、Github / Colabはシェアはできるけど、シェアして終わり、Kaggleはコンペありきのノートブックしかない(コンペ参加しなかったら見ないですよね普通)、という状況でした。また、シェアすることで収益化できるというサービスはありませんでした。 そこで、処理・アルゴリズムのTipsから、体系的なサマリー・教材など、あらゆる表現ができるノートブックを一瞬で記事としてシェアできて、誰でも収益化ができるサービスを今回作ってみました。 作ったもの:Notebox ノートブックsということでダジャレみたいですけど、Noteboxというサービスを作りました。ここでは簡単な使い方・機能を紹介したいと思います。 ローカルのノートブックを記事にする +ボタンを押すと作成画面に移動します。ここにローカルのノートブック(.ipynbで終わるもの)をアップすると、記事が自動で作成されます。タイトルとタグを付けて、右下の公開するボタンを押すとすぐに公開されます。 動画だとこんな感じです。 GitHubのノートブックを記事にする GitHub連携することで、GitHubにあるノートブックを簡単に記事にできます。現在は公開レポジトリのみ対応しています。 収益化する 3種類の収益化があります。 1. 投げ銭・・・知見の対価として読者が金額を決めてコメントとともに贈れます。 2. スーパーコメント・・・投げ銭とほとんど同じですが、コメント欄にYouTubeのスーパーチャットと同じように表示されます。(β版機能です。) 3. 有料記事・・・記事の設定で有料記事にもできます。無料公開の範囲も簡単に決めることができます。 最後に さまざまなサイエンス領域で自由に知見が共有され、投稿者にとってもオーディエンスにとっても実用的で心地の良い場所にしていけたらと思います。是非使ってみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コピペでYouTubeの動画をダウンロードする(Python)

コード jupyter_lab.ipynb !pip install youtube-dl import youtube_dl ydl_opts = { 'outtmpl':'保存先のパス/ファイル名.mp3' } with youtube_dl.YoutubeDL(ydl_opts) as ydl: ydl.download(['URL']) これで完成!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoの旅 ~Part6~ モデルのデータ一覧表示編

目標 追加したモデルをhtmlに一覧表示させる 流れ 1・ビューでモデルのデータ取り出し 2・テンプレートでモデルの一覧を表示 3・確認 コード解説 TestApp/APP/testapp/views.py from django.shortcuts import render from django.http import HttpResponse from .models import LikeFood #追加 # Create your views here. def index(request): foods = LikeFood.objects.all()#追加 context = { 'foods':foods#追加 } return render(request,'testapp/index.html',context) 1・ビューでモデルのデータ取り出し from .models import LikeFood ・・・modelsファイルから呼び出し foods = LikeFood.objects.all()・・・モデルのデータを全て取得 TestApp/APP/testapp/templates/testapp/index.html <body> <h1>好きな食べ物</h1> {% for food in foods %} <p>私が好きな食べ物は{{food.content}}です</p> {% endfor %} </body> 2・テンプレートでモデルの一覧を表示 for food in foods・・・foodsのデータから一行づつとり出している food.content・・・foodのcontentカラムだけ表示 $ python manage.py runserver 3・確認
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ipython 自作モジュールのパスを通して起動するTips

作成中のモジュールをちょっとテストしたい ということが多々あります。 ある程度開発が進んできたらちゃんとテストツールを使って試験データを作って、、、とすればいいのですけれど、まだそこまで仕様が固まってないとか、そもそもテストデータ作るほどの規模でもないしなーと言うくらいのときのTIPS。 どうするか? こうすればいいだけ。 > env PYTHONPATH="./" ipython ./ならパス通す必要なのでは? まぁ無いんですけど、ipythonの中に入ってからcdして目当てのスクリプトの入っているディレクトリに引っ越したり、あるいは同一ディレクトリ以外に配置したモジュール使ったりというのは往々にしてあるわけで、そういうときに起動前にワンライナーでPYTHONPATH与えておけばOKです。 そんだけ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoの旅 ~Part5~ 管理ユーザー登録編

目標 管理ユーザーを登録してモデルにデータを追加 手順 1・管理ユーザーをコマンドを用いて登録 2・管理サイトにアクセス 3・管理サイトに自作したモデルを追加 コード解説 $ cd APP $ python manage.py createsuperuser Username: 何でもいい Email address: 何でもいい Password: 何でもいい Password (again): 何でもいい Superuser created successfully. 1・管理ユーザーをコマンドを用いて登録 Username・Emailアドレス・パスワード を任意で設定 $ python manage.py runserver 2・管理サイトにアクセス http://127.0.0.1:8000/admin/でアクセス まずログインページになると思うので 先ほど設定したUsernameとパスワードを打ち込む APP/testapp/admin.py from django.contrib import admin from .models import LikeFood#追加 admin.site.register(LikeFood)#追加 3・管理サイトに自作したモデルを追加 同じアプリケーション内にある APP/testapp/models.pyに記入しているモデルを 同じ階層のadmin.pyで呼び出して登録!!! 管理サイトに戻ってリロード 先ほど追加したDB・モデルが操作できるようになる!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebSpeechAPIを使ってブラウザにQiita記事をしゃべらす

誰もが一度はQiita記事をラジオのように音で聞けたらなと思ったことがあると思う(ない) 先日、TwitterでWebSpeachAPIというJavaScriptのライブラリを使って簡単にブラウザを喋らせることができると話題になっていた。(ずいぶんと前からあったっぽいが) とても面白そうなんだけど、なにをしゃべらせばいいのか思いつかなかったからQiita記事をしゃべらせてみた つくったもの app.py from flask import Flask from flask import render_template, request from bs4 import BeautifulSoup import requests app = Flask(__name__) @app.route('/', methods=['GET']) def index(): return render_template('index.html') @app.route('/', methods=['POST']) def url_post(): qiita_url = request.form.get('qiita_url') # スクレイピングするURLを指定しHTMLを取得 res = requests.get(str(qiita_url)) # BeautifulSoupオブジェクトをつくる soup = BeautifulSoup(res.text, 'html.parser') # クラス名を指定して要素を取得 speech_title = soup.find("h1",class_='css-cgzq40').get_text() speech_text = soup.find("section",class_='it-MdContent').get_text() context = { 'title': speech_title, 'text': speech_text } return render_template('index.html', context=context) if __name__ == '__main__': app.run(host='0.0.0.0', port='5000') template/index.html <html> <body> <h2>ブラウザにQiita記事をしゃべらす</h2> <form action="/" method="POST" enctype="multipart/form-data"> <input type="url" name="qiita_url"> <input type="submit" value="決定"> </form> {% if context %} <div> <h4>{{context.title}}</h4> <button id="speech-btn">再生</button> <button id="cancel-btn">停止</button> <button id="pause-btn">一時停止</button> <button id="resume-btn">再開</button> <p id="text">{{context.text}}</p> </div> {% endif %} </body> {% if context %} <script> const text = document.getElementById("text").textContent const speechBtn = document.querySelector('#speech-btn') const cancelBtn = document.querySelector('#cancel-btn') const pauseBtn = document.querySelector('#pause-btn') const resumeBtn = document.querySelector('#resume-btn') // 再生 speechBtn.addEventListener('click', function() { const uttr = new SpeechSynthesisUtterance(text) speechSynthesis.speak(uttr) }) // 停止 cancelBtn.addEventListener('click', function() { speechSynthesis.cancel() }) // 一時停止 pauseBtn.addEventListener('click', function() { speechSynthesis.pause() }) // 再開 resumeBtn.addEventListener('click', function() { speechSynthesis.resume() }) </script> {% endif %} </html> 全体はFlaskを使ってて、その中でBeautifulSoupを使って指定のQiita記事をスクレイピングして、喋らせたい要素をWebSpeechAPIに渡してる感じ ただChromeだとうまく動作しなかった、なんか声の種類が関係しているっぽい 動作確認済の環境 Win : Edge iOS : safari まとめ しゃべらせるものを間違えてる感が半端なくて個人的にはとても好き 思ったよりちゃんと喋っててて驚き (初投稿頑張った)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ディレクトリ構造をMappingとして読み取れるやつ作った

ユニットテストを書くとき、ストレージへの書き出し状況が意図通りであるという点を見たい場合があります。 それを簡単にするために、ディレクトリツリーの現在の状況をMappingとして読み出せるクラスを作りました。ライブラリにしてPyPIに出すほどではない規模なのでここにコード書いちゃいますね。ご自由にお使いください。 これで、 assert PathMapping(tmp_dir) == {'xx': b'yyyyyy'} なんていう簡単な式でストレージ書き出しを検証できるようになります。パラメータ化テストならこの右辺をパラメータ化できるので複雑な書き出しがあるモジュールのテストでもパラメータ化していろんな動作条件での出力を簡潔な記述で網羅できるようになりますね。 class PathMapping(Mapping): """ ディレクトリパスを再帰的なマッピングとして扱う (文字列をキーに、ディレクトリはマッピング、ファイルはbytes) 例: my_home ├ foo │ └ bar.txt (内容は "some content") └ baz.txt (内容は "another content") というディレクトリ構造のとき、以下の等式が成立 PathMapping(Path('my_home')) == { 'foo': { 'bar.txt: b'some content\n' }, 'baz.txt': b'another content\n' } """ def __init__(self, path: Path): self._path = path def __getitem__(self, k: str): child = (self._path / k) if child.is_dir(): return PathMapping(child) elif child.is_file(): return child.read_bytes() else: raise KeyError def __len__(self) -> int: return len(list(self._path.iterdir())) def __iter__(self) -> Iterator[str]: for child in self._path.iterdir(): yield child.name このクラスのオブジェクトが吐き出すkey-valueデータはオブジェクト生成時点のものではなく、今現在のディレクトリツリー状況です。 そのことがどう生きてくるかというと、pytestのこんなfixtureを作る場合です。 @fixture def mock_なんかストレージに書き出すモジュール(monkeypatch): """ このfixtureオブジェクトそのものをMappingとしてアクセスすると書き出し結果がみられます """ with TemporaryDirectory as tmp_dir: ... # 「なんかストレージに書き出すモジュール」 が tmp_dir に書き出すように ... # monkeypatchでいろいろ乗っ取っている yield PathMapping(Path(tmp_dir)) こうしておくと、ユニットテスト本文側では def test_XXX(mock_なんかストレージに書き出すモジュール): ... # ターゲットを呼び出している ... # ターゲットは「なんかストレージに書き出すモジュール」を叩いている # 検証:想定通り書き出されていること assert mock_なんかストレージに書き出すモジュール == {'xx': b'yyyyyy'} と書けます。PathMappingオブジェクトを作った時点ではなく、assertのところまで来た時点でのディレクトリツリー状況が読み出せていますので、こういうことができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

intel realsenseD435Iで路面状況をセンシングしてみた!

はじめに  クルマイスの自動運転で必要な舗装路面から脱輪しないように、画像情報から方向修正を促す機能を作ってみた。 ※今回、初投稿により見苦しい点があるかもしれないが、御容赦願いたい 開発環境&使用機材  開発環境: ・python3   ・Anaconda Powershell Prompt (anaconda3) 使用機材: intel realsenseD435i どうやって舗装路面と未舗装路面見分ける?  舗装路面から脱輪しないようにするために、色相の変化に着目して画像センシングすることにした。具体的にはアスファルト=黒、未舗装=緑という基本パターンを見分けられるように画像を2値化して、その2値化画像を基に方向修正の判断を行う。 プログラム import pyrealsense2 as rs import numpy as np # Import OpenCV for easy image rendering import cv2 pipeline = rs.pipeline() config = rs.config() pipeline_wrapper = rs.pipeline_wrapper(pipeline) pipeline_profile = config.resolve(pipeline_wrapper) device = pipeline_profile.get_device() device_product_line = str(device.get_info(rs.camera_info.product_line)) found_rgb = False for s in device.sensors: if s.get_info(rs.camera_info.name) == 'RGB Camera': found_rgb = True break if not found_rgb: print("The demo requires Depth camera with Color sensor") exit(0) config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) if device_product_line == 'L500': config.enable_stream(rs.stream.color, 960, 540, rs.format.bgr8, 30) else: config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) # Start streaming profile = pipeline.start(config) depth_sensor = profile.get_device().first_depth_sensor()#深度スケールをメー トルで取得 depth_scale = depth_sensor.get_depth_scale() print("Depth Scale is: " , depth_scale) clipping_distance_in_meters = 0.25 #排除する距離パラメータ[m] clipping_distance = clipping_distance_in_meters / depth_scale #depth基準作り align = rs.align(align_to) c=0 try: while True: #データ取得 frames = pipeline.wait_for_frames() # frames.get_depth_frame() is a 640x360 depth image # Align the depth frame to color frame aligned_frames = align.process(frames) # Get aligned frames aligned_depth_frame = aligned_frames.get_depth_frame() # Dデータ color_frame = aligned_frames.get_color_frame() #RGBデータ # Validate that both frames are valid if not aligned_depth_frame or not color_frame: continue depth_image = np.asanyarray(aligned_depth_frame.get_data()) color_image = np.asanyarray(color_frame.get_data())#RGB表示3ch height, width = depth_image.shape #height, width, ch = color_image.shape #print(ch) depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.5), cv2.COLORMAP_BONE)#α=透明度 COLORMAP_BONE #画質変更 h=320 w=240 dst = cv2.resize(color_image, dsize=(w, h)) dst_depth = cv2.resize(depth_image, dsize=(w, h)) depth_gry = cv2.resize(depth_colormap,dsize=(w, h)) # ぼかし加工。 for i in range(4): average_square_size = 10 #ぼかしパラメータ 大きくする程にぼけていくdef=15 sigma_color = 5000 # 色空間に関する標準偏差パラメータ 大きくすると色の平滑化範囲を広げるdef=5000 sigma_metric = 1# 距離空間に関する標準偏差パラメータ 大きくすると色の平滑化範囲を広げる (d==0の時のみ有効) img_bilateral = cv2.bilateralFilter(dst,average_square_size,sigma_color,sigma_metric) hsv_img = cv2.cvtColor(img_bilateral, cv2.COLOR_BGR2HSV) #HSVモデルに変更 img_gly = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)#グレースケール # Remove background - Set pixels further than clipping_distance to grey white_color = 255 #RGBでの白色 depth_image_2d = np.dstack((dst_depth,dst_depth,dst_depth)) # 配列同士を奥行きで重ねる。RGBに対してdepth情報追加 bg_removed = np.where((depth_image_2d > clipping_distance) | (depth_image_2d < 0), depth_gry, hsv_img)#引数1 条件式 #引数2 条件合致時の置き換え #引数3 条件不一致時の置き換え #2値処理 lower = (0, 7, 140) #引数1色相 引数2彩度 引数3明度 色相は赤→黄→緑→水色→青→紫でループする upper = (359, 255, 255) bin_img = cv2.inRange(bg_removed, lower, upper) #2値化 引数1 画像指定 引数2 下限の色 引数3 上限の色 contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE ) #輪郭検出 contours = list(filter(lambda x: cv2.contourArea(x) > 200000, contours)) # 小さい輪郭は誤検出として削除する #輪郭処理 xl=20 #左判定ポイント座標設定パラメータ xr=w-xl #右判定ポイント座標設定パラメータ yr1=yl1=5 #判定ポイント座標設定パラメータ yr2=yl2=7 #判定ポイント座標設定パラメータ if 255==bin_img[yr1,xl] and 255==bin_img[yl1,xr]: #前判定ゾーンで監視+距離データ(depth_image_3d)で判断する? print('止まってください') data = "止まってください" # 送信データの作成 elif 255==bin_img[yr1,xr] and 255==bin_img[yr2,xr]: #引数1 y座標 引数2 x座標 print('左に行ってください') data = "左に行ってください" # 送信データの作成 elif 255==bin_img[yl1,xl] and 255==bin_img[yl2,xl] : #左判定ゾーンで監視 print('右に行ってください') data = "右に行ってください" # 送信データの作成 else: print('正常') data = "正常" # 送信データの作成 #処理画像表示部分 cv2.namedWindow('RGB', cv2.WINDOW_NORMAL) cv2.imshow('RGB',dst)#2値化(bin_img) RGB(dst) cv2.namedWindow('RGB-D', cv2.WINDOW_NORMAL) cv2.imshow('RGB-D',bin_img)#2値化(bin_img) RGB(dst) #処理画像保存部分 c+=1 write_file_name = f'RGB{c:05d}.jpg' write_file_name1 = f'RGB-D{c:05d}.jpg' cv2.imwrite('./AIdata002/'+write_file_name, dst) cv2.imwrite('./AIdata002/'+write_file_name1, img_gly) key = cv2.waitKey(5) # Press esc or 'q' to close the image window if key & 0xFF == ord('q') or key == 27: cv2.destroyAllWindows() break finally: pipeline.stop() ざっくり解説  主な流れ: 1.カメラの初期設定        2.画像データ(RGB-D)を取得        3.データのリサイズ+平滑処理        4.色相に応じた2値化処理        5.4箇所のピクセルで白or黒の検出して方向修正処理        6.カメラでセンシングした画像の表示&ファイル保存            
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mayaでDeadLineを使ってみる1

機会があったので、mayaで DeadLine submitterを使って何ができるのかを試してみる。 submitterのインストール 割愛。 とりあえず SubmitJobToDeadline で呼び出せる sbumitterのオプションを読み解いてみる Job Descripton Job Name ジョブの名前。 ファイル名にした方が良いらしい。 Comment ジョブに関するコメント。 Department ジョブの分類。 Job Scheduling Pool Secondary Pool Group 処理に使用する workerグループの指定。 Priority ジョブの優先順位の指定。数字が大きい方が先の処理される。 戦争の原因になるので、よほどのことがない限りは一律50にしておいた方がいい。 例)自分のタスクだけ優先順位を上げる。 -> 戦争 Machine Limit 同時に使用するworker数の指定。 これも戦争の原因になりそうなので、ルールの指定が必要。 例)ヘビーなタスクを全マシン使用して数日間占有 -> 戦争 Concurrent Tasks Task Timeout Minimum Task Time Enable Auto Task Timeout Limit Group Dependencies 親となるjobを指定 Machine List 処理に使用するマシンのリスト。 Groupを指定してる場合は、そのGroupに属しているマシンを選択しないとジョブが開始されない。 On Complete job終了時の挙動。 nothing そのまま archive アーカイブ送り delete 削除 Machine List is a Blacklist Machine Listをブラックリストとして使用。 Machine Listに指定されているマシンは処理に使用しない。 Sbumit As Suspended サブミット直後のjobのステータスを サスペンドにするか否か Close on Submission Precache assets for AWS Render Options Frame List レンダリング設定で連番レンダリングが指定されている場合に編集可能。 レンダリングするフレームを設定 Frame Per Task AdditionalFrameOptions camera 使用するカメラを指定 Project Path mayaのプロジェクトパス。 ネットワーク上からアクセスできる場所を指定。 Output Path 処理の成果物を格納するフォルダ。 ネットワーク上からアクセスできる場所を指定。 Maya Build 処理するmayaの指定。 32 or 64 bit submit Maya Scene File シーンファイルをサブミットする? ネットワーク上に保存してなくても飛ばせる? Use MayaBatch Plugin True の時は Plugin=MayaBatch が使用される Falseの時は Plugin=MayaCmd が使用される Ignore Error Code 211 Strict Error Checking startup script use MayaBatch Plugin == True 時に使用可能。 実行されるタイミングは、サブミットしたファイルが展開された後。 ファイル選択ダイアログにてpythonファイルが指定できるようになっているが、実際には mel で souce $filePath; で実行されるので、事実上はpython非対応?。 スクリプトファイルをネットワーク上からアクセスできる場所に格納しておかないと、ワーニングが出る。無視して実行するとエラーになる。 Command Line Args use MayaBatch Plugin == False 時に使用可能。 Deadline job Type Deadlineに処理させるジョブのタイプ 今回は Maya Render Job / Maya Script Job / Alenbic Export Job を検証予定 Maya Render Job Threads Submit Render Layers As Separate Jobs レンダーレイヤー毎にjobを分解してサブミットする。 分解されたジョブは指定したジョブ名をDependenciesとして、 ジョブ名 + レイヤー名でサブミットされる Override Layer Job Settings レンダーレイヤー毎にジョブの設定を細かく行うか否か。 行う場合は別ダイアログが開く。 Submit Cameras As Separate Jobs カメラ毎にjobを分解してサブミットする Ignore Default Cameras デフォルトカメラ(top / left / front / persp) を使用しない Enable Local Rendering Render Hair Frames 参考 https://forums.thinkboxsoftware.com/t/mayabatch-usersetup-py/13237
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はぐれメタル討伐と負の二項分布(SFC版ドラクエ2RTAを例に)

概要 負の二項分布が全然覚えられないので、はぐれメタルに助けてもらうことにした。 背景 筆者が統計学を勉強した際、負の二項分布がどうしても頭に入らなかった。というのも教科書等で紹介される例が淡白で、あまり印象に残らなかったためである。そこで昔遊んだことのあるSFC版ドラクエ2を題材に、筆者にとって親しみやすい負の二項分布の例を作ってみようと思い記事作成に至った。 RTA世界最速記録更新の難しさ ドラクエ2のRTA1では、今なお何人ものRTAプレイヤーが世界最速記録2の更新を目指している。不慮の全滅やちょっとしたプレイミスが起これば、すぐに冒険の初めからやり直しとなる厳しい世界である。一体どれほど挑戦すれば新記録樹立の夢が叶うのだろうか? 記録更新の鍵として、「満月の塔」というダンジョンで莫大な経験値を持つ「はぐれメタル」というモンスターをいかに早く複数匹倒せるかが重要である。このモンスターはレアエンカウントにも関わらずよく逃げるので、中々倒すことができない。 はぐれメタル討伐は運要素が非常に大きいため、RTAでは短時間で倒せず足切り3になることが日常茶飯事である。熟達したRTAプレイヤーにとっては、この難所さえスムーズにクリアできれば記録更新の可能性が大幅に上昇するため、「短時間でのはぐれメタル討伐成功」は「世界最速記録を出す」ための必要条件の1つと考えて良い。 本記事の目的 諸条件のもと、はぐれメタル目標討伐数に達するまでの戦闘回数とその確率を負の二項分布で表すことで、世界最速記録更新の難易度を推し量るとともに、負の二項分布自体に親しみを持てるようにする。 計算に必要な諸情報 計算に必要な各種数値を以下に示す。(ドラクエ2における基本的な戦闘システム4の説明は省略する。) 各種確率 項目 ラベル(計算で利用) 確率 はぐれメタルとの遭遇確率5 $p_{find}$ 1/10 戦闘開始時の先制攻撃確率6 $p_{sensei}$ 1/32 戦闘開始時の不意打ち確率6 $p_{fuiuchi}$ 1/32 各ターンにおけるはぐれメタル行動先制確率7 - 1 各ターンにおけるはぐれメタル非逃走確率7 $p_{stay}$ 1/2 会心の一撃確率8 $p_{kaishin}$ 1/32 はぐれメタルに対して会心の一撃が出た際の討伐確率7 - 1 はぐれメタルが攻撃を避ける確率9 - 0 その他数値 項目 値 戦闘中の各ターンで攻撃可能な味方パーティの人数 (人) 3 はぐれメタルHP (ポイント)10 4~5 各ターンで味方1人が通常攻撃ではぐれメタルに与えるダメージ量 (ポイント)9 1 はぐれメタル目標討伐数 (匹)11 3 注意 エンカウント開始時、味方パーティは常に3人生存しているとする。 はぐれメタルは常に1匹のみ単独で戦闘に出現する(他の敵は同時には出現しない。) はぐれメタルとの戦闘中、味方パーティは戦闘不能にならないものとする。 はぐれメタルとの戦闘中、味方パーティは常に全員が通常攻撃を行うとする。 1回のエンカウントにおけるはぐれメタル討伐確率 ランダムエンカウント12発生からはぐれメタル討伐までのフローチャートは全部で6パターンある。(前項の諸条件より、味方パーティが攻撃可能なターンを2周回すことができれば、はぐれメタルを確実に倒せることに注意する。) パターン フローチャート 発生確率 (%)        1        はぐれメタル遭遇 → 通常戦闘 → はぐれメタル非逃走 → 味方パーティ3人による通常攻撃 → はぐれメタル非逃走 → 討伐        2.1        2 はぐれメタル遭遇 → 通常戦闘 → はぐれメタル非逃走 → 誰かが会心の一撃 → 討伐 0.43 3 はぐれメタル遭遇 → 先制攻撃 → 味方パーティ3人による通常攻撃 → はぐれメタル非逃走 → 討伐 0.14 4 はぐれメタル遭遇 → 先制攻撃 → 誰かが会心の一撃 → 討伐 0.028 5 はぐれメタル遭遇 → 不意打ち → はぐれメタル非逃走 → はぐれメタル非逃走 → 味方パーティ3人による通常攻撃 → はぐれメタル非逃走 → 討伐 0.034 6 はぐれメタル遭遇 → 不意打ち → はぐれメタル非逃走 → はぐれメタル非逃走 → 誰かが会心の一撃 → 討伐 0.0069 (合計) 2.8 発生確率の導出 パターン$i$の確率を$p_{i}$と表現すると、 \begin{eqnarray} p_{1} &=& p_{find} \times (1 - p_{sensei}) \times (1 - p_{fuiuchi}) \times p_{stay} \times (1 - p_{kaishin})^{3} \times p_{stay} \\ &\simeq& 2.1 \times 10^{-2} \\ p_{2} &=& p_{find} \times (1 - p_{sensei}) \times (1 - p_{fuiuchi}) \times p_{stay} \times \{1-(1 - p_{kaishin})^{3}\} \\ &\simeq& 4.3 \times 10^{-3}\\ p_{3} &=& p_{find} \times p_{sensei} \times (1 - p_{kaishin})^{3} \times p_{stay} \\ &\simeq& 1.4 \times 10^{-3} \\ p_{4} &=& p_{find} \times p_{sensei} \times \{1-(1 - p_{kaishin})^{3}\} \\ &\simeq& 2.8 \times 10^{-4} \\ p_{5} &=& p_{find} \times (1 - p_{sensei}) \times p_{fuiuchi} \times p_{stay} \times p_{stay} \times (1 - p_{kaishin})^{3} \times p_{stay} \\ &\simeq& 3.4 \times 10^{-4} \\ p_{6} &=& p_{find} \times (1 - p_{sensei}) \times p_{fuiuchi} \times p_{stay} \times p_{stay} \times \{1-(1 - p_{kaishin})^{3}\} \\ &\simeq& 6.9 \times 10^{-5} \\ \end{eqnarray} である。したがって、(満月の塔4F/5Fにおいて)1回のランダムエンカウントではぐれメタルを討伐できる確率$p$は、 p = \sum_{i=1}^{6}p_{i} \simeq 2.8 \times 10^{-2} となる。 負の二項分布の確率質量関数 1回のランダムエンカウントにおいて確率$p$ではぐれメタル討伐に成功するとき、討伐失敗エンカウントを$r$回経て目標討伐数$k$匹を達成する確率(確率質量関数)$ f(r)$は、 f(r) = {}_{k+r-1} \mathrm{C}_{r}p^k(1-p)^r と表され、負の二項分布に従う13。($k$, $p$については、$k = 3$, $p \simeq 2.8 \times 10^{-2}$で定数とみなす。) 例えば$r=0$のときは、無駄なエンカウントは一切なく、狩場の初回戦闘からいきなり3連続のストレートではぐれメタルを狩れるケースである。(すなわち、討伐成功エンカウントを○、討伐失敗エンカウントを×とすれば、○○○となるケース。) このようなケースが発生する確率は、 \begin{eqnarray} f(r=0) &\simeq& {}_{3+0-1} \mathrm{C}_{0}\times(2.8\times 10^{-2})^3\times\{1-(2.8\times 10^{-2})\}^0 \\ &\simeq& 2.1 \times 10^{-5} \\ \end{eqnarray} である。 $r=1$のときは、1~3回目までのいずれかのエンカウントで、はぐれメタル討伐に1回失敗(残り2回は成功)し、4回目のエンカウントではぐれメタル討伐に成功するケースである。(すなわち、○○×○, ○×○○, ×○○○のいずれかが発生するケース。) このようなケースが発生する確率は、 \begin{eqnarray} f(r=1) &\simeq& {}_{3+1-1} \mathrm{C}_{1}\times(2.8\times 10^{-2})^3\times\{1-(2.8\times 10^{-2})\}^1 \\ &\simeq& 6.2 \times 10^{-5} \\ \end{eqnarray} である。 負の二項分布の累積分布関数 確率質量関数$f(r)$は、ちょうど$r$回の討伐失敗エンカウントを経た末に目標討伐数を達成するような確率を表しているが、$r$回以内の討伐失敗エンカウントで目標討伐数達成確率を見れるようにした方が実用上は便利である。そのような確率(累積分布関数)を$F(r)$とする。 例えば、討伐失敗エンカウント1回以内に目標討伐数を達成する確率は、$r=0$と$r=1$のときの確率質量関数$f(r)$を足せばよいので F(r=1) = f(r=0) + f(r=1) \simeq 8.3 \times 10^{-5} 討伐失敗エンカウント2回以内に目標討伐数を達成する確率は、$r=0$と$r=1$と$r=2$のときの確率質量関数$f(r)$を足せばよいので F(r=2) = f(r=0) + f(r=1) + f(r=2) \simeq 2.0 \times 10^{-4} 結局のところ、$r=n$回以内に目標討伐数を達成するような確率は、 F(r=n) = f(r=0) + f(r=1) + \dots +f(r=n) = \sum_{r=0}^{n}f(r) である。 確率質量関数と累積分布関数のグラフ 討伐失敗エンカウント$r = 0, 1, 2, \dots, 500$に対応する確率求めて図示すると以下のようになる。 Fig.1 確率質量関数(Fig.1左図) $f(r)$が最大値を取るのは、$r=70$のときで確率は$f(r=70)\simeq7.6\times10^{-3}$である。すなわち、70回の討伐失敗エンカウントを経たときが、はぐれメタル目標討伐数(3匹)を最も達成しやすくなる。 また、期待値$E$は、 E = \frac{k(1-p)}{p} \simeq 105 であり、平均的には105回の討伐失敗エンカウントを積み重ねた末に、3匹目のはぐれメタルを討伐することができる。 累積分布関数(Fig.1右図) $F(r)$は$r=94$のときに初めて50%を超える。$r=300$まで討伐失敗エンカウントを許容すると99%に達し、目標討伐数はほぼ確実に達成可能となる。 現在の世界最速記録 執筆時点(2021-07-25)でのSFC版ドラクエ2RTA2の世界最速記録は2:56:32である14。このときのプレイ動画14を見ると、満月の塔の狩場(4F/5F)に入ってから、討伐失敗エンカウント23回を経てはぐれメタル3匹目の討伐に成功していたことがわかった。(討伐成功エンカウントを○、討伐失敗エンカウントを×とすれば、××××××××××××××○××××××××○×○という戦績で3匹のはぐれメタル討伐を達成。) これがどれほどラッキーな事象なのかを調べてみる。 以下に、前項のFig.1を$0\le r\le50$の範囲に拡大したグラフを示す。 Fig.2 討伐失敗エンカウント数を23回以内に抑える確率は、Fig.2右図において F(r=23) \simeq 3.4 \times 10^{-2} であり、世界最速記録達成時にはこの3.4%ほどの確率を引き当てたことになる。新たな記録更新を狙う際もこのレベルで「運の良さ」を引き寄せることが1つの目安になると考えられる。 「100回に3回当たるならそこまで大したことない」と思われる方もいるかもしれないが、はぐれメタル討伐は長い冒険のごく一部を占めるに過ぎない。はぐれメタルの狩場に着くまでも、無事討伐に成功して狩場を出た後も、大小様々な難所があることを考えると、この1箇所だけで勝率3.4%程度のギャンブルに勝たなければならないことは、むしろ相当に厳しい壁と言える。 さいごに 本記事では負の二項分布の実例としてはぐれメタル討伐に言及した。筆者個人としては次なるRTA世界最速記録更新の可能性を何となく掴めたし、負の二項分布に十分親しみを感じることもできたので目的は果たせた。 余談 筆者はRTAプレイヤーではなく、たまたまYouTubeのおすすめに出てきた動画14を視聴して同作品のRTAの存在を知った素人に過ぎない。同作品をプレイしたのは人生で2,3回くらいだと思う。 筆者の思うRTAの面白さは、はぐれメタル討伐成功を待ち侘びるギャンブル的な要素もさることながら、どんな状況でも高速で行動の最適解を積み重ねて行くところにある。本記事のテーマ上、「運の良さ」というRTAの一側面だけに言及したため補足。 備考 目標討伐数$k = 1, 2, 3, 4, 5$のときの確率分布を以下に示す。 目標討伐数        $0 \le r \le 500$               $0 \le r \le 50$                $k=1$         $k=2$ $k=3$(再掲) $k=4$ $k=5$ 計算に用いたPythonのコードを記載する。(Pythonのバージョン: 3.8.10) ## 計算に必要な各種数値 # はぐれメタルとの遭遇確率 p_find = 1/10 # 戦闘開始時の先制攻撃確率 p_sensei = 1/32 # 戦闘開始時の不意打ち確率 p_fuiuchi = 1/32 # 各ターンにおけるはぐれメタル非逃走確率 p_stay = 1/2 # 会心の一撃確率 p_kaishin = 1/32 ## ランダムエンカウント発生からはぐれメタル討伐までのフローチャートとその確率 # はぐれメタル遭遇 → 通常戦闘 → はぐれメタル非逃走 → 味方パーティ3人による通常攻撃 → はぐれメタル非逃走 → 討伐 p1 = p_find * (1 - p_sensei) * (1 - p_fuiuchi) * p_stay * (1-p_kaishin)**3 * p_stay # はぐれメタル遭遇 → 通常戦闘 → はぐれメタル非逃走 → 誰かが会心の一撃 → 討伐 p2 = p_find * (1 - p_sensei) * (1 - p_fuiuchi) * p_stay * (1-(1-p_kaishin)**3) # はぐれメタル遭遇 → 先制攻撃 → 味方パーティ3人による通常攻撃 → はぐれメタル非逃走 → 討伐 p3 = p_find * p_sensei * (1-p_kaishin)**3 * p_stay # はぐれメタル遭遇 → 先制攻撃 → 誰かが会心の一撃 → 討伐 p4 = p_find * p_sensei * (1-(1-p_kaishin)**3) # はぐれメタル遭遇 → 不意打ち → はぐれメタル非逃走 → はぐれメタル非逃走 → 味方パーティ3人による通常攻撃 → はぐれメタル非逃走 → 討伐 p5 = p_find * (1 - p_sensei) * p_fuiuchi * p_stay * p_stay * (1-p_kaishin)**3 * p_stay # はぐれメタル遭遇 → 不意打ち → はぐれメタル非逃走 → はぐれメタル非逃走 → 誰かが会心の一撃 → 討伐 p6 = p_find * (1 - p_sensei) * p_fuiuchi * p_stay * p_stay * (1-(1-p_kaishin)**3) # 1回のランダムエンカウントではぐれメタルを討伐する確率 p = p1 + p2 + p3 + p4 + p5 + p6 ## グラフ描画 import matplotlib.pyplot as plt import numpy as np from scipy.stats import nbinom plt.rcParams['font.size'] = 28 def plot_nbinom(x_mesh, k, p): """はぐれメタル討伐に関する確率質量関数と累積分布関数のプロット x_mesh [numpy.ndarray]: はぐれメタルの討伐失敗エンカウント数 k [int]: はぐれメタル目標討伐数 p [float]: 1回のランダムエンカウントにおけるはぐれメタル討伐確率 """ y_mesh = nbinom.pmf(x_mesh, k, p) fig, ax = plt.subplots(1, 2, figsize=(24, 10)) plt.suptitle(f'はぐれメタルを{k}匹討伐するまでの討伐失敗エンカウント数とその確率について') # 確率質量関数 ax[0].vlines(x=x_mesh, ymin=0, ymax=y_mesh, lw=8, color='lightsteelblue') ax[0].set_xlabel('討伐失敗エンカウント数 (回)') ax[0].set_ylabel('発生確率') ax[0].set_title('確率質量関数') ax[0].set_xlim(0, x_mesh[-1]) ax[0].set_ylim(0, ax[0].get_ylim()[1]) # 累積分布関数 ax[1].vlines(x=x_mesh, ymin=0, ymax=y_mesh.cumsum(), lw=8, color='lightsteelblue') ax[1].set_xlabel('討伐失敗エンカウント数 (回)') ax[1].set_ylabel('発生確率') ax[1].set_title('累積分布関数') ax[1].set_xlim(0, x_mesh[-1]) ax[1].set_ylim(0, ax[1].get_ylim()[1]) return y_mesh, fig, ax # はぐれメタル目標討伐数 k = 3 # 討伐失敗エンカウント数: 0 ~ 500 x_mesh = np.arange(0, 500 + 1, 1) y_mesh, fig, ax = plot_nbinom(x_mesh, k, p) plt.show() # 討伐失敗エンカウント数: 0 ~ 50 x_mesh = np.arange(0, 50 + 1, 1) y_mesh, fig, ax = plot_nbinom(x_mesh, k, p) plt.show() リアルタイムアタック(Real Time Attack)のこと。 ↩ 本記事では、SFC版ドラクエ2RTAの「状況再現不使用」カテゴリについて言及する。 ↩ 世界最速記録更新の見込みがなくなり、プレイ続行を断念(冒険の書を消去)すること。 ↩ Wikipedia『ドラゴンクエストII_悪霊の神々』を参照。 ↩ 満月の塔4F/5Fのはぐれメタルの遭遇確率。データ置き場『エリア別PT構成・出現率計算機』を参照。 ↩ 『DQ1,2(SFC)その他解析まとめ』を参照。 ↩ 『SFC版DQ2RTA計算機』を参照。はぐれメタル行動先制確率については味方の素早さが上がれば1未満となり得るが、低レベルで進行するRTAでは、満月の塔の討伐の際、はぐれメタルが確実に先行すると考えて良い。 ↩ 『DQ各作品全般のメモ帳』を参照。 ↩ はぐれメタルが攻撃を避ける確率は実際はゼロではないが、計算を簡単にするためこのような設定にした。偶奇技というテクニックを使うと2ターンで最低5ダメージを確実に与えることができることと、非マヌーサ状態での会心の一撃は必中である(注:はぐれメタルは状態異常攻撃をしてこない)ことを考慮すると、本記事の計算上は「はぐれメタルの通常攻撃回避率はゼロ」と見做しても問題がない。 ↩ 『ドラゴンクエスト2 RTAチャート (SFC版)』を参照。 ↩ いくつかRTA動画を視聴したところ、満月の塔では2~4匹ほど狩るのが主流であったため目標討伐数を3匹に設定した。 ↩ エンカウントに利用される乱数については、『DQ1,2(SFC)乱数生成について』を参照。 ↩ 二項分布の導出については、『負の二項分布の意味と期待値、分散』を参照。 ↩ RTAプレイヤーのヌル氏により2021-01-08に達成。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Piで水やりをIT化にしましょう

背景 最近家のベランダでスイカとレモンを田植えています。在宅勤務で毎日の朝一水やりをするのはやや面倒だと思います。また、連続の外出日には水やりを止めることになりますので、リモートで水やりをできるIoT仕組みを作ったらどうと思い始めました。 完成品は以下になります。 準備するもの ラズベリーパイ3 x 1 Arduino ELEGOO UNO R3 x 1 Arduino USB コード x 1 湿度センサー x 1 ブレイブボード x 2 マイクロウォーターポンプ x1 ポケットバテッリー 単3形乾電池 x 4 プラスチックケース x 2 ボウル x 1 ゴムパイプ x 1 5v リレー x 1 ジャンパワイヤ オス-オス 複数 ジャンパワイヤ オスーメス 複数 ブレイブボード2枚を用意したのは、ワニクリップはないからです。またメスーメスのジャンパワイヤがあれば、配線はもっとシンプル化にできます。 成果 ハードウェア 配置 ※図にある湿度センサーは土に挿入する状態になります。 ロジック ポケットバッテリーからラズベリーパイに電源を供給します 単3電池からマイクロウォーターポンプに電源を供給します リレーのOPEN,CLOSEから、マイクロウォーターポンプのON,OFFをコントロールしています。 ラズベリーパイのGPIO 21からリレーのOPEN CLOSEをコントロースしています。 Arduinoは湿度センサーから土の湿度を図ます ArduinoはUSBを経由でラズベリーパイに湿度情報を送信します ソフトウェア ロジック インフラ側ロジック ラズベリーパイ側はngrokを利用し、イントラネットの浸透がおこなっています サーバーロジック ラズベリーパイはサーバー側として動作します ラズベリーパイ側はflaskを利用し、GPIO 21をコントロールするapiを提供します ラズベリーパイ側はgrpc サーバーストリングを利用し、クライアント側に湿度を送信します クライアント側ロジック flutterを利用して、iosアプリを開発します サンプルコード ngrok ngrokでイントラネットの浸透をします # flaskサーバーのポートは5000になっています ./ngrok http 5000 # 湿度grpcサーバー側のポートは5000になっています ./ngrok tcp -region jp 50051 ラズベリーパイ iot_project_server Arduino arduino Flutter iot_project_app 課題 電源長時間供給 ポケットバッテーリーは数時間しか続けられないため、業務化にするためにわ、バッテーリーだけではなく、一般的な家庭用電源につながる必要があります。現在では、家のスイカはベランダーに置いてあるため、家の電源とどうやって繋がっていくことをあらかじめ考えることが必要です。 複数植物対応 いまは一つのボルトから一つの植物に水やりしか対応できないため、一つのボルトから複数の植物に水やりできるように改善する必要があります。 安全なスレッド対策 いまは、GPIOを管理するスイッチ変数はメモリに保存されています、もし同時複数のクライアントからリクエストが来たら、メモリに保存されてあるスイッチ変数は同時に変更されてしまいますので、あらかじめスレッドロックをかけるようにしなければなりません。 感想 中国のIT業界では最近「内巻」ということばは流行っておりまして、それは一つの分野に対して、参戦する人は多くなって、しかも皆技術のボルトネックからさらに伸ばさないため、みんなさん知恵で勝負するより、時間と体力で勝負することにしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Athenaで基礎からしっかり入門 分析SQL(Python・Pandasコード付き) #2

今まで複雑なデータ操作・分析などはPythonでやっており、SQLは普通のアプリ開発程度のライトなものしか触って来なかったのですが、やはり分析用の長いSQLなども書けた方がやりとり等で便利・・・という印象なので、復習も兼ねて記事にしておきます。 また、SQLに加えて検算も兼ねてPythonやPandasなどを使ったコードもSQLと併記していきます(Pythonで書くとどういった記述が該当するのかの比較用として使います)。 ※長くなるのでいくつかの記事に分割します。本記事は2記事目となります。 他のシリーズ記事 前回(#1) 用語の説明・SELECT、WHERE、ORDER BY、LIMIT、AS、DISTINCT、基本的な集計関係(COUNTやAVGなど)、Athenaのパーティション、型、CAST、JOIN、UNION(INTERSECTなど含む)などについてはこちらの記事で既に説明済みなので本記事では触れません。 本記事で触れる点 GROUP BY HAVING サブクエリ CASE COALESCE NULLIF LEAST GREATEST 四則演算などの基本的な計算 日付と日時の各操作 ※ボリューム的には最後の日付と日時の操作関係で半分以上を占めています。 環境の準備 以下の#1の記事でS3へのAthena用のデータの配置やテーブルのCREATE文などのGitHubに公開しているものに関しての情報を記載していますのでそちらをご参照ください。 特記実行 お仕事がAWSなので合わせてDBはAWSのAthena(Presto)を利用していきます。BigQueryやRedshift、MySQLやPostgreSQLなどではある程度方言や使える関数の差などがあると思いますがご了承ください。 同様にお仕事がゲーム業界なので、用意するデータセットはモバイルゲームなどを意識した形(データ・テーブル)で進めます。 エンジニア以外の方(プランナーさんやマーケの方など)も少し読者として想定しています。ある程度技術的なところで煩雑な記述もありますがご容赦ください。 長くなるのでいくつかの記事に分割して執筆を進めています。 同じ値をグループ化する: GROUP BY GROUP BYを使うと各カラムで同じ値のものをグループ化することができます。これによって例えば「ユーザーごとの売り上げを計算したい」とか「日付ごとの売り上げを出したい」といったように、一度のSQLでいくつものグループ(ユーザー単位であったり日付ごとであったりのグループ)を作って計算なとを行うことができます。 使い方はGROUP BY <グループ化で使いたい対象のカラム名>といった具合に書きます。例えばユーザーごとにグループ化したければユーザーIDのカラムを指定する形でGROUP BY user_idといった記述になります。 例として、2021-01-01と2021-01-02の2日間で各ユーザーが何回ログインしているのかを集計してみましょう。 各ユーザーのログインを扱うloginというテーブルを使っていきます。loginテーブルは以下のように各ユーザーのID(user_id)や日時などのカラムを持っています。 SELECT * FROM athena_workshop.login LIMIT 10; ユーザーごとにグループ化してそれぞれのユーザーでのログイン回数を出したいのでGROUP BY user_idという記述を使います。 また、テーブルの性質上各ユーザーごとの行数がそのまま各ユーザーのログイン回数となるので、COUNT(*)の記述を使って行数をカウントしています。そのままだとCOUNTで指定したカラムが_col1といった名前で表示されてしまうので、AS login_countと指定してなんのカラムなのかが分かりやすくしています。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login WHERE dt IN ('2021-01-01', '2021-01-02') GROUP BY user_id これで特定の期間におけるログイン回数を出すことができました。 Pythonでの書き方 Pandasにgroupbyメソッドがあるのでそちらを使います。by引数でグループ化で使いたいカラム名を指定します。このメソッドではDataFrameGroupByやSeriesGroupByといったインスタンスの型が返却され、そちらに対してcountやsumなどの集計関数(メソッド)を実行することで通常のデータフレームなどが返ってきます。by引数で指定したカラムはインデックスに設定された状態で返ってきます。もしSQLのようにインデックスではなくカラムに設定したい場合には別途reset_indexなどのメソッドを利用する必要があります。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df = df[['user_id', 'time']] df = df.groupby(by='user_id').count() df.rename(columns={'time': 'login_count'}, inplace=True) print(df.head(5)) login_count user_id 1 1 3 2 5 1 6 3 9 2 他の句との順番に注意 WHERE句やLIMIT句などをGROUP BYと一緒に使う場合にはそれぞれの句の順番を意識する必要があります。順番が正しくないとエラーになってしまいます。 まずはWHERE句ですが、これはGROUP BYよりも前に書かないといけません。 以下のようにGROUP BYの後に書くとエラーになります。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login GROUP BY user_id WHERE dt IN ('2021-01-01', '2021-01-02') ORDER BYに関してはGROUP BYの直後に書きます。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login WHERE dt IN ('2021-01-01', '2021-01-02') GROUP BY user_id ORDER BY user_id GROUP BYの前に書くとエラーになります。 LIMIT句に関しては最後に書きます。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login WHERE dt IN ('2021-01-01', '2021-01-02') GROUP BY user_id ORDER BY user_id LIMIT 3 複数のカラムを指定する GROUP BYには複数のカラムを指定することができます。この場合グループ化の条件が増えるので、1つ辺りのグループの行数は基本的に減少します。例えばAカラムとBカラムという2つのカラムを指定した場合、「A且つB」という2つの条件を満たした行同士が同じグループとなります。 例えば「日別」且つ「ユーザーごと」といったように日付やユーザーIDといった複数の条件を付けて集計をやりたいケースなどはよく発生します。 書き方はORDER BYなどで複数のカラムを指定するときと同様に、半角のコンマ区切りで複数のカラムを指定します。 以下のSQLではユーザーID(user_id)と日付(dt)のパーティションをGROUP BYに指定しています。分かりやすいようにユーザーはIDが1と3のユーザーのみに絞ってあります。 SELECT user_id, dt, COUNT(*) AS login_count FROM athena_workshop.login WHERE user_id IN(1, 3) GROUP BY user_id, dt ORDER BY dt, user_id Pythonでの書き方 groupbyメソッドのby引数にはリストなども指定できるので、リスト等で複数のカラム名を指定すれば対応ができます。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df = df[['user_id', 'device_type', 'time']] df = df.groupby(by=['user_id', 'device_type']).count() df.rename(columns={'time': 'login_count'}, inplace=True) print(df.head(5)) login_count user_id device_type 1 2 1 3 2 2 5 1 1 6 2 3 9 2 2 基本的には利用できるカラムはGROUP BYで指定したカラムもしくは集計関数が利用できる GROUP BYを使った場合、SELECT ... FROMの...のカラムの指定部分は以下の2つの条件を満たすカラムのみ指定ができます。 GROUP BYで指定したカラム GROUP BYで指定していないカラムで、且つ集計関数(SUMやCOUNTなど)を使う場合 COUNTなどの集計関数であればアスタリスクの全カラム指定も使える それぞれ試していきましょう。まずはGROUP BYで指定されたカラムのケースです。以下のように通常通りSQLを投げることができます。 SELECT user_id FROM athena_workshop.login WHERE user_id IN(1, 3) GROUP BY user_id 続いて集計関数を挟む形のSQLです。サンプルとしてCOUNTの集計関数を使います。カラムはGROUP BYで指定していないdtのパーティションを指定しています。こちらも通常通りSQLが流れます。 SELECT user_id, COUNT(dt) AS login_count FROM athena_workshop.login WHERE user_id IN(1, 3) GROUP BY user_id 続いて指定できない条件を試してみます。GROUP BYに指定されておらず、且つ集計関数も挟まない形のdtのパーティションを指定してみます。 SELECT user_id, dt FROM athena_workshop.login WHERE user_id IN(1, 3) GROUP BY user_id 以下のようにエラーになります。 エラーメッセージに以下のように出ています。 SYNTAX_ERROR: line 1:17: 'dt' must be an aggregate expression or appear in GROUP BY clause dtのカラム(or パーティション)は集計(aggregate)表現(集計関数)もしくはGROUP BY句(clause)でしか使えませんよ、といったことが表示されています。 このようにGROUP BYを使った場合には使えるカラムの挙動が特殊になります。 GROUP BYした集計関数の値に対して条件を設定する: HAVING GROUP BYでグループ化し、集計関数を指定したカラムに対して条件を指定したくなることも結構出てきます。例えば以下のユーザーごとのログイン回数を集計するSQLで、ログイン回数が3回以上のユーザーの行のみを抽出したいとします。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login WHERE dt IN ('2021-01-01', '2021-01-02') GROUP BY user_id しかしながらGROUP BYを使った場合にはWHERE句でそのカラムを指定することはできません。以下のようにWHERE login_count >= 3としてみてもエラーになります。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login WHERE dt IN ('2021-01-01', '2021-01-02') AND login_count >= 3 GROUP BY user_id そういった場合はHAVINGを使うとグループ化し集計関数を挟んだ値に対して条件を設定することができます。HAVINGはGROUP BYの後に書きます。また、左辺などに集計関数と対象のカラムの指定を行います。残りと等値や以下・超過などの条件の書き方はWHERE句と同じような書き方となります。 以下のSQLでは先ほどWHERE句関係でエラーになったものを対応でき、ログイン回数が3回以上の行のみが抽出できています。 SELECT user_id, COUNT(*) AS login_count FROM athena_workshop.login WHERE dt IN ('2021-01-01', '2021-01-02') GROUP BY user_id HAVING COUNT(*) >= 3 クエリ結果を参照する: サブクエリ この節以降ではメインのSQLとは別のSQLを同時に使い、結果をメインのSQLで使う形となるサブクエリについて触れていきます。SQL内で複数のSELECT文などが出現する形となります。 サブクエリ使用時の前提 サブクエリ使用時の前提と留意点として以下のようなものがあります。 基本的にはWHERE句やJOINなどに直接サブクエリを書かずにWITH句などを使う方がSQLがシンプルで読みやすくなることが多めです。どうしてもサブクエリで複数のクエリを使うと複雑になりがちで、且つWHERE句などにぎっしり詰まると他の人がそのSQLを読んだりが難しくなったりして好ましくないケースがあり注意が必要です。 WHERE句などでのサブクエリはJOINなどで代替できればそちらの方がシンプルになることが多いように感じます。参考 : WHERE句のサブクエリは大抵の場合テーブルJOINで代替できる サブクエリが増えれば増えるほどSQLの内容が遅くなったりスキャンサイズが増えたりしてきます。繰り返し参照するものや過程の検算などをしたい場合には後々触れる一時テーブル(中間テーブル)を作成する対応(CTAS)の方が好ましいケースも結構あるかもしれません。 クエリ結果を条件指定(WHERE)で利用する まずはサブクエリをWHERE句で使う方法について見ていきます。サブクエリ側は一つのカラムしか選択はできません。 且つ一番基本的な使い方としては、サブクエリ側の行は1行のみとする形となります。 説明のため、以前の記事でJOINの説明時に使ったサンプルテーブルを使っていこうと思います。join_sample_left_tableとjoin_sample_right_tableという2つのテーブルを使います。 join_sample_left_tableテーブルは以下のように武器のIDと名前を格納したテーブルになります。 SELECT * FROM athena_workshop.join_sample_left_table join_sample_right_tableテーブルは以下のように武器のIDと攻撃力を持ったテーブルになります。 SELECT * FROM athena_workshop.join_sample_right_table join_sample_right_tableテーブル側(攻撃力のデータを持つテーブル)で、武器の名前がグングニルになっている行の攻撃力を確認したいとします。もちろんIDを確認してIDの方をWHERE句に使ったりもできますが、今回はIDを見ずにSQLを投げたいとします。 その場合以下のようなサブクエリを使ったSQLを書くことでWHERE句に武器名を指定しつつSQLを実行することができます。 SELECT * FROM athena_workshop.join_sample_right_table WHERE id = (SELECT id FROM athena_workshop.join_sample_left_table WHERE name = 'グングニル') ポイントとしてはWHERE id = (<サブクエリ>)となっている点です。WHERE句部分のカラム名の指定とサブクエリで指定しているカラム名は別の名前でも動きますが、型が同一で且つサブクエリ側は1つのカラムのみ、且つ今回の基本的なサブクエリではサブクエリの結果の行も1行のみにする必要があります。 サブクエリ側で別のテーブルを参照して名前に対してWHERE句で条件を指定し、結果のIDがメインのSQLのWHERE句の条件として使われる・・・という形になります。 他にも色々機能はあるのですが、前述の通りWITH句を使った方が読みやすかったりJOINで代替できるケースも多かったりで仕事でもあまりこのサブクエリは使う機会が無いので本記事ではあまり触れずにいこうと思います(読んだ分析のSQL本でも基本的にWITH句や中間テーブルなどがメインに使われており、WHERE句のサブクエリは軽く触れる程度だったため)。 クエリ結果の列を連結(JOIN)する こちらもWHERE句と同様、WITH句で対応したり普通にJOINしたりすることが仕事では多いのですが一応軽く触れておきます。 以下のようにJOINするテーブルの指定部分にサブクエリで別のSELECT文を流しています。サブクエリ側の結果はWHERE句の指定で武器名がグングニルとなっている行のみとなるのと、内部結合(INNER JOIN)させているので結果的にグングニルの1行のみ残ります。 これだけだとサブクエリ使わずに連結してからWHERE句とかで制御すれば同じ結果が得られるのですが、もっと複雑な加工をサブクエリ側でやる場合などに役立ったりするケースがあります(ただし、それらの場合でもWITH句などを使う方が読みやすくなるかもしれません)。 SELECT * FROM athena_workshop.join_sample_right_table INNER JOIN ( SELECT * FROM athena_workshop.join_sample_left_table WHERE name = 'グングニル' ) USING(id) WITHを使うと読みやすいSQLになる WITH句というものを使ってサブクエリを書くこともできます。クエリ結果を一時的なテーブルとして任意の名前を付けることができ、そのテーブルをメインのSQL内で利用することができます。 WITH <設定する一時テーブル名> AS (サブクエリのSELECT文) メインのSELECT文といったように書きます。サブクエリの結果の一時テーブル名に名前を付けるフォーマットになっていることから分かる通り、サブクエリの結果に変数や定数のように名前を付けられます。つまりサブクエリの結果を一時的なテーブルとして、その名前を使ってメインのSQL内の各所で利用することができます。 プログラミングでいうとWITH句を使わないサブクエリがハードコーディング、WITH句を使ったものが変数などを利用したようなものに近くなります。WITH句を使うことで以下のようなメリットがあります。 複数個所でサブクエリの結果が必要になった場合にも毎回サブクエリを書かずに済みます(サブクエリの記述の重複を避けれます)。 結果の一時テーブルに名前を付けられるので、なんの内容のサブクエリなのかが分かりやすくなります。 サブクエリとメインのSQLの各SELECT文を分離できるので、メインのSQLがごちゃっとしにくくなります(WHERE句やJOIN部分に詰め込まれず、分かれる形になります)。 WITH句を使わずに書いていたサブクエリの内容をWITH句を使って同様の内容を記述すると以下のようになります。 WITH target_weapon AS ( SELECT * FROM athena_workshop.join_sample_left_table WHERE name = 'グングニル' ) SELECT * FROM athena_workshop.join_sample_right_table INNER JOIN target_weapon USING(id) 条件に応じた制御を行う この節以降では条件に応じた制御について触れていきます。 条件に応じた値の置換を行う: CASE CASEを使うと条件に応じた特定の列の値を別のカラムの値として結果に設定することができます。プログラミングでいうところのif文みたいなことができます。 基本的な書き方としてはSELECT ... FROMのカラム指定部分にCASE WHEN <対象のカラム> = <対象値> THEN <条件を満たした場合に設定される値> ENDといったように書きます。 Pythonのif文で書くと if <対象のカラムの値> == <対象値>: <条件を満たした場合に値を設定する処理> みたいなものに近くなります。 また、ENDの後にはASを使って結果の値のカラム名を設定することができます。 説明のため引き続きJOINなどの節で使ったjoin_sample_right_tableテーブルを利用していきます。以下のようなテーブルとなっています。武器の攻撃力を想定したテーブルですね。 SELECT * FROM athena_workshop.join_sample_right_table 例として、このテーブルでIDはあるものの武器名などが設定されているマスタのテーブルはAthenaの環境に無くJOINができない・・・しかし武器名などを設定したいといったケースを想定してみましょう(マスタではなく種別値などのラベルなどはテーブルが存在しないケースなどは結構あると思います)。 こういったケースでは数が少なければCASEで対応することができます。例えば以下のSQLではCASEを使ってIDが2の武器の行に対してグングニルという武器名を設定しています。そちらの武器名のカラム名にはweapon_nameという名前をASを使って設定しています。 SELECT *, CASE WHEN id = 2 THEN 'グングニル' END AS weapon_name FROM athena_workshop.join_sample_right_table Pythonでの書き方 データフレームに対する条件分岐的な制御は色々書き方はあります。maskを使ったりwhereを使ったりapplyを使ったり、もしくはリストに直してループで回してしまうなど様々です。 今回はmaskメソッドを使います。特定のシリーズ(カラム)でmaskメソッドを使い、第一引数にスライスなどで使う時と同じように条件(今回はdf['id'] == 2)を指定し、第二引数に設定する値(今回は'グングニル')を指定します。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/join_sample_right_table/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['weapon_name'] = df['id'].mask(df['id'] == 2, 'グングニル') print(df) id attack weapon_name 0 2 1000 グングニル 1 3 800 3 複数の条件の値の置換を行う 複数の条件を設定したい場合にはENDの前にWHEN ... THEN ...の記述を追加していきます。Pythonで言うところのelifのような処理になります。 SELECT *, CASE WHEN id = 2 THEN 'グングニル' WHEN id = 3 THEN 'トライデント' END AS weapon_name FROM athena_workshop.join_sample_right_table どの条件にも該当しない場合の設定 いくつかWHEN ... THEN ...で条件を設定したとして、「その他」のケースで値を設定したいケースにはELSEを使います。どの条件にも該当しない場合にはその行がELSEで指定された値にて置換されます。ELSE <設定する値>といったように書きます。WHEN ... THEN ...の条件の後、且つENDの前に書きます。 SELECT *, CASE WHEN id = 1 THEN 'エクスカリバー' WHEN id = 2 THEN 'グングニル' ELSE '不明な武器' END AS weapon_name FROM athena_workshop.join_sample_right_table 欠損値(NULL)の場合に別の値に置き換える: COALESCE 欠損値(NULL)の行の値を別の値に置き換えたい場合にはCALESCE関数を使います。COALESCEは「 癒合(ゆごう)する、合体する、合同する」といった意味を持つ単語のようです。発音的にはコウアレスとかに近いようです。 coalesceの読み方はカタカナで書くと「コウアレス」に近いようです。 SQL関数coalesceの使い方と読み方 以下のように前記事で扱った、欠損値が一部で存在するdevice_unique_idを使っていきます。 SELECT * FROM athena_workshop.device_unique_id WHERE unique_id IS NULL LIMIT 10 COALESCE関数を使うと欠損している値を別の値に置換できます。第一引数には対象のカラム名、第二引数には欠損値に対して設定する値を指定します。以下では分かりやすいように欠損している箇所に対して不明な値という値を設定しています。 SELECT *, COALESCE(unique_id, '不明な値') AS label FROM athena_workshop.device_unique_id WHERE unique_id IS NULL LIMIT 10 ログが欠損値を含む場合に別のラベルに置換しておきたい場合であったり、他の不正なケースの値と統一したい(例 : 空文字に統一したり、何らかの欠損値を示す文字列と統一したり)といった場合に役立ちます。 Pythonでの書き方 fillnaメソッドで欠損値補完が効くのでそちらを使います。第一引数に設定する値を指定します。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/device_unique_id/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['unique_id'].fillna('不明な値', inplace=True) print(df[df['unique_id'] == '不明な値'].head()) user_id date device_type unique_id 1065 7367 2021-01-01 2 不明な値 1201 6101 2021-01-01 1 不明な値 1759 17509 2021-01-01 1 不明な値 1830 20110 2021-01-01 2 不明な値 1858 20459 2021-01-01 1 不明な値 条件を満たした値を欠損値(NULL)に置き換える: NULLIF NULLIF関数はCOALESCEと逆のような挙動をします。特定の条件を満たす行を欠損値に置換します。 第一引数に対象のカラム名、第二引数に該当する条件の値を指定します。 以下のSQLではdevice_typeが2の行の値を欠損値にしています。 SELECT user_id, device_type, NULLIF(device_type, 2) AS nullif_column FROM athena_workshop.device_unique_id LIMIT 10 Pythonでの書き方 replaceメソッドで置換が利くので置換の値でNumPyの欠損値(np.nan)を指定すれば同じようなことができます。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/device_unique_id/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['device_type'].replace(to_replace=2, value=np.nan, inplace=True) print(df.head(10)) user_id date device_type unique_id 0 9958 2021-01-01 1.0 80dd469bc6fc1ac5c51bfca801c605d6cb296868a64471... 1 26025 2021-01-01 1.0 d302032f6b82abaf18f9df6bbde6ba999b901b7a8def00... 2 28390 2021-01-01 NaN 4ebfcc61825f46ca981eb56041ed837b1ff5bd08c495e4... 3 11977 2021-01-01 1.0 de24cf852f0270fe864de27b633e888d57ab55278b0030... 4 10732 2021-01-01 1.0 3420f6554809274949fab197b685f549e2b7d94ccb58f0... 5 16670 2021-01-01 NaN 0a2e7ebfa13f583361d4dc51a7ddab03acaeb1700c90d5... 6 17426 2021-01-01 NaN 60c4893828c76fb134247d773c02b8bf0c4801c95d392d... 7 26623 2021-01-01 1.0 eeb5d2235bb2db0c87f016d0bbe1050e9f08d7767dd5fc... 8 2083 2021-01-01 NaN 4e7a0668e1de434a88c26ae248ab80e71f3dc5ec603421... 9 9079 2021-01-01 NaN fae26219b0f6340c86bb7440576dbef94afa7ca5809d0b... 値が小さい方を設定する: LEAST LEAST関数を使うと、指定されたカラムや数値などの中から最小の値が選択されます。以下のSQLではtotal_salesというカラムの値と100という値の2つの中で小さい方が結果に残っていることを確認できます。特定のカラムに対して上限値などを設定する時などに便利かもしれません。 SELECT user_id, total_sales, LEAST(total_sales, 100) AS least_result FROM athena_workshop.user_total_sales_and_power LIMIT 50; なお、この関数は3つ以上の引数も受け付けてくれます。複数のカラムなどを指定して、その中から一番小さい値を残す・・・といった制御ができます。 SELECT user_id, total_sales, power, LEAST(total_sales, power, 50000) AS least_result FROM athena_workshop.user_total_sales_and_power LIMIT 50; Pythonでの書き方 minメソッドでaxis=1と指定すれば列方向での最小値が取れます。既存のカラム以外にも固定値も必要な場合にはそのカラムを追加しておくことで対応ができます(以下のコードではdf['fixed_value'] = 50000として固定値のカラムを追加しています)。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['fixed_value'] = 50000 df['least_result'] = df[['total_sales', 'power', 'fixed_value']].min(axis=1) print(df.head(10)) user_id date device_type total_sales power fixed_value least_result 0 19914 2021-01-01 1 0 39203 50000 0 1 19005 2021-01-01 2 0 42748 50000 0 2 5741 2021-01-01 2 0 69826 50000 0 3 14028 2021-01-01 2 0 102833 50000 0 4 18434 2021-01-01 2 0 72106 50000 0 5 6231 2021-01-01 1 0 68877 50000 0 6 7403 2021-01-01 1 82900 223433 50000 50000 7 23378 2021-01-01 2 0 110646 50000 0 8 9597 2021-01-01 1 30300 70400 50000 30300 9 1553 2021-01-01 2 0 62792 50000 0 値が大きい方を設定する: GREATEST GREATEST関数はLEAST関数の逆の挙動をします。つまり指定されたカラムや値の中で一番大きな値が選択されます。 LEASTとは逆に最小値を固定値で設定したい場合などに便利です。 SELECT user_id, total_sales, power, GREATEST(total_sales, power, 50000) AS least_result FROM athena_workshop.user_total_sales_and_power LIMIT 50 Pythonでの書き方 LEASTの時にminメソッドを使っていた箇所をmaxメソッドにするだけです。axis=1などの引数設定は変わりません。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['fixed_value'] = 50000 df['least_result'] = df[['total_sales', 'power', 'fixed_value']].max(axis=1) print(df.head(10)) user_id date device_type total_sales power fixed_value least_result 0 19914 2021-01-01 1 0 39203 50000 50000 1 19005 2021-01-01 2 0 42748 50000 50000 2 5741 2021-01-01 2 0 69826 50000 69826 3 14028 2021-01-01 2 0 102833 50000 102833 4 18434 2021-01-01 2 0 72106 50000 72106 5 6231 2021-01-01 1 0 68877 50000 68877 6 7403 2021-01-01 1 82900 223433 50000 223433 7 23378 2021-01-01 2 0 110646 50000 110646 8 9597 2021-01-01 1 30300 70400 50000 70400 9 1553 2021-01-01 2 0 62792 50000 62792 基本的な演算 この節以降では四則演算などについて触れていきます。 加算 特定のカラムに対して+の記号を使って加算(足し算)を行うことができます。例えば<特定のカラム> + 10000とすると指定したカラムに1万足された状態で各行が返ってきます。 以下のSQLではadded_valueというカラム名を付ける形で元のカラムよりも1万足された値を表示しています。 SELECT user_id, total_sales, total_sales + 10000 AS added_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 また、固定値だけでなくカラム同士の足し算なども可能です。以下のSQLではtotal_salesカラムとpowerカラム同士を足し算した結果を表示しています。 SELECT total_sales, power, total_sales + power AS added_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 3つ以上の複数の加算を繋げることも可能です。以下のSQLでは2つのカラムに加えて固定値の1万をさらに加えています。 SELECT total_sales, power, total_sales + power + 10000 AS added_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 このようにカラム同士を計算したり複数の演算子を繋げたりすることは他の計算(減算や乗算など)でも可能です。 Pythonでの書き方 そのまま+や+=などの演算子で計算ができます。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['added_value'] = df['total_sales'] + 10000 print(df.head(10)) user_id date device_type total_sales power added_value 0 19914 2021-01-01 1 0 39203 10000 1 19005 2021-01-01 2 0 42748 10000 2 5741 2021-01-01 2 0 69826 10000 3 14028 2021-01-01 2 0 102833 10000 4 18434 2021-01-01 2 0 72106 10000 5 6231 2021-01-01 1 0 68877 10000 6 7403 2021-01-01 1 82900 223433 92900 7 23378 2021-01-01 2 0 110646 10000 8 9597 2021-01-01 1 30300 70400 40300 9 1553 2021-01-01 2 0 62792 10000 減算 -の記号を使うと減算(引き算)できます。 SELECT total_sales, total_sales - 5000 AS subtracted_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 Pythonでの書き方 こちらも-や-=などの演算子を使って対応ができます。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['subtracted_value'] = df['total_sales'] - 5000 print(df.head(10)) user_id date device_type total_sales power subtracted_value 0 19914 2021-01-01 1 0 39203 -5000 1 19005 2021-01-01 2 0 42748 -5000 2 5741 2021-01-01 2 0 69826 -5000 3 14028 2021-01-01 2 0 102833 -5000 4 18434 2021-01-01 2 0 72106 -5000 5 6231 2021-01-01 1 0 68877 -5000 6 7403 2021-01-01 1 82900 223433 77900 7 23378 2021-01-01 2 0 110646 -5000 8 9597 2021-01-01 1 30300 70400 25300 9 1553 2021-01-01 2 0 62792 -5000 乗算 *の演算子で乗算(掛け算)できます。 SELECT total_sales, total_sales * 3 AS multiplied_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 Pythonでの書き方 *や*=の演算子で対応ができます。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['multiplied_value'] = df['total_sales'] * 3 print(df.head(10)) user_id date device_type total_sales power multiplied_value 0 19914 2021-01-01 1 0 39203 0 1 19005 2021-01-01 2 0 42748 0 2 5741 2021-01-01 2 0 69826 0 3 14028 2021-01-01 2 0 102833 0 4 18434 2021-01-01 2 0 72106 0 5 6231 2021-01-01 1 0 68877 0 6 7403 2021-01-01 1 82900 223433 248700 7 23378 2021-01-01 2 0 110646 0 8 9597 2021-01-01 1 30300 70400 90900 9 1553 2021-01-01 2 0 62792 0 除算 /の記号を使うと除算(割り算)することができます。 SELECT power, power / 2 AS divided_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 気を付けないといけない点として、整数の型のカラム(intやbigintなど)に対して除算を行うと結果も整数になります。つまり小数点以下が切り捨てとなります。 前述したSQLの結果で102833という値の2での除算結果が51416となっている点に注目してください。51416.5といったように浮動小数点数にはなりません。Python2系のような挙動ですね。これはAthena(Presto)特有の挙動というものでもなく、他の例えばPostgreSQLなどでも同様に切り捨ての挙動となります。 結果を浮動小数点数(floatやdoubleなど)で取得したい場合には前回の記事で触れたように対象のカラムを浮動小数点数に型変換(キャスト)してから除算する必要があります。 以下のSQLでは事前に浮動小数点数の型であるdoubleにキャストしています。 SELECT power, CAST(power AS double) / 2 AS divided_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 Pythonでの書き方 /や/=の記号などで対応ができます。Python(Pandas)側は除算しても切り捨てにはならずに浮動小数点数の値となります。Pandasなどを使わずに且つPython2系などを使って除算をすると切り捨てにはなります。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['divided_value'] = df['power'] / 2 print(df.head(10)) user_id date device_type total_sales power divided_value 0 19914 2021-01-01 1 0 39203 19601.5 1 19005 2021-01-01 2 0 42748 21374.0 2 5741 2021-01-01 2 0 69826 34913.0 3 14028 2021-01-01 2 0 102833 51416.5 4 18434 2021-01-01 2 0 72106 36053.0 5 6231 2021-01-01 1 0 68877 34438.5 6 7403 2021-01-01 1 82900 223433 111716.5 7 23378 2021-01-01 2 0 110646 55323.0 8 9597 2021-01-01 1 30300 70400 35200.0 9 1553 2021-01-01 2 0 62792 31396.0 SQL側と合わせて除算結果を切り捨てにしたい場合にはastypeメソッドを使ってintにキャストすれば切り捨てが実行されます。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['divided_value'] = df['power'] / 2 df['divided_value'] = df['divided_value'].astype(int, copy=False) print(df.head(10)) 0 19914 2021-01-01 1 0 39203 19601 1 19005 2021-01-01 2 0 42748 21374 2 5741 2021-01-01 2 0 69826 34913 3 14028 2021-01-01 2 0 102833 51416 4 18434 2021-01-01 2 0 72106 36053 5 6231 2021-01-01 1 0 68877 34438 6 7403 2021-01-01 1 82900 223433 111716 7 23378 2021-01-01 2 0 110646 55323 8 9597 2021-01-01 1 30300 70400 35200 9 1553 2021-01-01 2 0 62792 31396 剰余 剰余(余り)を計算したい場合には%の記号を使います。 SELECT power, power % 100 as modulo_value FROM athena_workshop.user_total_sales_and_power LIMIT 50 Pythonでの書き方 %の記号を使ったりなどで対応ができます。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_total_sales_and_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['divided_value'] = df['power'] % 100 print(df.head(10)) user_id date device_type total_sales power divided_value 0 19914 2021-01-01 1 0 39203 3 1 19005 2021-01-01 2 0 42748 48 2 5741 2021-01-01 2 0 69826 26 3 14028 2021-01-01 2 0 102833 33 4 18434 2021-01-01 2 0 72106 6 5 6231 2021-01-01 1 0 68877 77 6 7403 2021-01-01 1 82900 223433 33 7 23378 2021-01-01 2 0 110646 46 8 9597 2021-01-01 1 30300 70400 0 9 1553 2021-01-01 2 0 62792 92 日付と日時の操作 この節以降では日付(date)と日時(timestamp)関係の操作について触れていきます。 必要なキャスト処理 ここまでの節で触れてきた各テーブルは日付も日時も文字列のカラムとして扱ってきました。そのため日付や日時に対する操作を行う場合には以下のようにCAST関数を使って型をDATEやTIMESTAMPなどに変換してから扱う必要があります。 日付に対するキャスト例 : SELECT date, sales, CAST(date AS DATE) FROM athena_workshop.total_sales_daily LIMIT 10; 日時に対するキャスト例 : SELECT user_id, time, CAST(time AS TIMESTAMP) FROM athena_workshop.login LIMIT 10; Pythonでの書き方 pd.to_datetime関数に特定の日付や日時になっている文字列のカラムのシリーズなどを引数に渡すとPandasのTimestamp型の値が取れます。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) print(df) print(type(df.at[0, 'date'])) date sales 0 2021-01-01 768500 <class 'pandas._libs.tslibs.timestamps.Timestamp'> 日数を加算する <日付もしくは日時> + INTERVAL '<日数>' DAYとすると対象のカラムの日付もしくは日時から指定された日数分あとの日付や日時が取得できます。例えばdateという日付のカラムに対してCAST(date AS DATE) + INTERVAL '2' DAYとすると2日後の日付が取れます。日付と日時問わずに利用できます。 2日後の日付を取得する例 SELECT date, sales, CAST(date AS date) + INTERVAL '2' DAY AS two_days_after FROM athena_workshop.total_sales_daily LIMIT 10; 2日後の日時を取得する例 SELECT user_id, time, CAST(time AS TIMESTAMP) + INTERVAL '2' DAY AS two_days_after FROM athena_workshop.login LIMIT 10; Pythonでの書き方 PandasのDayクラスを使うと日付の加算ができます。引数を省略すると+1日されます。 import pandas as pd from pandas.tseries.offsets import Day df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += Day() print(df) date sales 0 2021-01-02 768500 引数に日数を指定した場合はその日数分加算を行うことができます。 import pandas as pd from pandas.tseries.offsets import Day df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += Day(3) print(df) date sales 0 2021-01-04 768500 時間を加算する <日時> + INTERVAL '時間' HOURとすると対象の日時から指定された時間分加算した日時が取得できます。例えばtimeという日時のカラムに対してCAST(time AS TIMESTAMP) + INTERVAL '2' HOURとすると2時間後の時間が取れます。 SELECT user_id, time, CAST(time AS TIMESTAMP) + INTERVAL '2' HOUR AS two_hours_after FROM athena_workshop.login LIMIT 10; Pythonでの書き方 PandasでHourクラスがあるのでそちらを加算することで対応ができます。 import pandas as pd from pandas.tseries.offsets import Hour df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['time'] = pd.to_datetime(df['time']) df['time'] += Hour(2) print(df.head()) user_id time device_type 0 8590 2021-01-01 02:00:01 1 1 568 2021-01-01 02:00:02 1 2 17543 2021-01-01 02:00:04 2 3 15924 2021-01-01 02:00:07 1 4 5243 2021-01-01 02:00:09 2 年の加算 <対象の日付もしくは日時> + INTERVAL '<年数> YEAR'とすると年の加算ができます。 SELECT date, sales, CAST(date AS date) + INTERVAL '1' YEAR AS next_year FROM athena_workshop.total_sales_daily LIMIT 10; Pythonでの書き方 そういえば年での加算をPythonで扱ったことがない・・・気がしたのですが、調べたらYearクラスは存在しない?ようです。しかしDateOffsetクラスにyears引数を指定することで同じようなことができるようです(DateOffsetクラスを初めて使いました・・・)。 import pandas as pd from pandas.tseries.offsets import DateOffset df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += DateOffset(years=1) print(df) date sales 0 2022-01-01 768500 月の加算 <日付もしくは日時> + INTERVAL '<月数>' MONTHとすると月の加算処理を扱えます。 SELECT date, sales, CAST(date AS date) + INTERVAL '1' MONTH AS next_month FROM athena_workshop.total_sales_daily LIMIT 10; Pythonでの書き方 こちらも年と同様、MonthというクラスはPandasにはありませんがDateOffsetクラスのmonths引数を指定することで同じような対応を行うことができます。 import pandas as pd from pandas.tseries.offsets import DateOffset df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += DateOffset(months=1) print(df) date sales 0 2021-02-01 768500 複数の加算を同時に行う INTERVAL ...の記述は1つのカラムに対して複数の設定を同時に行えます。例えば1か月と2日後・・・といった設定が可能です。 SELECT date, sales, CAST(date AS date) + INTERVAL '1' MONTH + INTERVAL '2' DAY AS future_date FROM athena_workshop.total_sales_daily LIMIT 10 特定の部分(年や月など)の取得処理 ※この節で触れる方法以外にも、後々の節で触れる各種関数でも整数の形で取得することができます。 文字列の状態のカラムであればSUBSTRという関数で文字列の特定の位置の値が抽出できます。第一引数には対象のカラム、第二引数に文字列の開始位置、第三引数に抽出する文字列を指定します。文字列の開始位置の整数は1からスタートするので注意してください。また、文字列用の関数なので日付や日時にキャストしていない点にも注意してください。 例えばYYYY-MM-DD HH:MM:SSの形式の日時の文字列であれば年部分は1文字目以降・4文字分なのでSUBSTR(time, 1, 4)となります。月であれば6文字目・2文字なのでSUBSTR(time, 6, 2)となります。 SELECT user_id, time, SUBSTR(time, 1, 4) AS year, SUBSTR(time, 6, 2) AS month, SUBSTR(time, 9, 2) AS day, SUBSTR(time, 12, 2) AS hour, SUBSTR(time, 15, 2) AS minute, SUBSTR(time, 18, 2) AS second FROM athena_workshop.login LIMIT 10; Pythonでの書き方 日付もしくは日時に変換する前の文字列のカラムに対してPandasのシリーズのstr属性でスライスの記法([0:4]など)をすることで対応ができます。SQLのSUBSTR関数と異なりインデックスのスタートは1からではなく0からとなります。 import pandas as pd from pandas.tseries.offsets import DateOffset df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['year'] = df['time'].str[0:4] df['month'] = df['time'].str[5:7] df['date'] = df['time'].str[8:10] df['hour'] = df['time'].str[11:13] df['minute'] = df['time'].str[14:16] df['second'] = df['time'].str[17:19] print(df.head()) user_id time device_type year month date hour minute second 0 8590 2021-01-01 00:00:01 1 2021 01 01 00 00 01 1 568 2021-01-01 00:00:02 1 2021 01 01 00 00 02 2 17543 2021-01-01 00:00:04 2 2021 01 01 00 00 04 3 15924 2021-01-01 00:00:07 1 2021 01 01 00 00 07 4 5243 2021-01-01 00:00:09 2 2021 01 01 00 00 09 各種減算処理 減算も-の記号を使うことで加算と同様に行えます。加算と同様にYEAR, MONTH, DAY, HOURの指定に対応しており、使い方は加算と同じです。 SELECT date, sales, CAST(date AS date) - INTERVAL '1' MONTH - INTERVAL '2' DAY AS future_date FROM athena_workshop.total_sales_daily LIMIT 10 Pythonでの書き方 各種クラス(Dayなど)はそのまま減算のオペレーター(-や-=など)が使えます。 import pandas as pd from pandas.tseries.offsets import Day df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] -= Day(3) print(df) date sales 0 2020-12-29 768500 もしくはクラスの引数の値に負の値を指定しても同じ挙動になります。 import pandas as pd from pandas.tseries.offsets import Day df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += Day(-3) print(df) date sales 0 2020-12-29 768500 特定の部分の連結処理 例えばYYYY-MMの形式での年月部分だったり、MM/DDの月日だったりを抽出したいとします。YYYY-MMの年月の形式であれば前節で触れたSUBSTR関数だけでも対応ができます。ただしフォーマットが変わった場合(例えばYYYY年MM月といったような日本語表記が必要な場合など)には別の対応が必要になります。 そういった場合にはSUBSTR関数での抽出処理とCONCAT関数での連結処理を組み合わせることで対応ができます(英語でconcatenateは「連結する」という意味になります)。 CONCAT関数ではコンマ区切りで複数の文字列のカラムもしくは固定の文字列を指定することで、それぞれの値を連結した文字列を返してくれます。このCONCAT関数の中にSUBSTR関数のものなどを入れることで任意の部分・任意のフォーマットで文字列を出力することができます。 以下のSQLではYYYY年MM月というフォーマットで結果を出力しています。 SELECT date, sales, CONCAT(SUBSTR(date, 1, 4), '年', SUBSTR(date, 6, 2), '月') as year_and_month FROM athena_workshop.total_sales_daily LIMIT 10 もしくは||の記号を使っても文字列を連結することができます。 SELECT date, sales, SUBSTR(date, 1, 4) || '年' || SUBSTR(date, 6, 2) || '月' as year_and_month FROM athena_workshop.total_sales_daily LIMIT 10 Pythonでの書き方 シンプルに+の演算子だけで各カラムや特定の文字列を連結できます。 import pandas as pd from pandas.tseries.offsets import Day df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = df['date'].astype(str, copy=False) df['year'] = df['date'].str[0:4] df['month'] = df['date'].str[5:7] df['year_and_month'] = df['year'] + '年' + df['month'] + '月' print(df) date sales year month year_and_month 0 2021-01-01 768500 2021 01 2021年01月 日時の特定単位の切り落とし : DATE_TRUNC DATE_TRUNC 関数を使うと特定の単位での日付もしくは日時の値の切り捨て処理が行えます。TRUNCATEで「切り捨てる」といったような意味になります。 例えば月の単位で指定すれば日付以降が切り捨てられ、月初の日付などが得られます。日付の単位で指定すれば1日の始まりの日時が得られて時間や分などは切り捨てられます。 DATE_TRUNC('<対象の単位>', <日付もしくは日時>)という形で、第一引数に単位、第二引数に対象のカラム名などを指定します。 単位には以下のいずれかの文字列が指定できます。 'SECOND': 秒までが残ってミリ秒以下が切り捨てとなります。 'MINUTE': 分までが残って秒以下が切り捨てとなります。 'HOUR': 時までが残って分以下が切り捨てとなります。 'DAY': 日までが残って時以下が切り捨てとなります。 'WEEK': 月曜の開始時点の日付・日時になるように切り捨てとなります。 'MONTH': 月までが残って日以下が切り捨てとなります。 'QUARTER': 四半期ごとの開始の日付・日時になるように切り捨てとなります。 'YEAR': 年だけが残り、他は切り捨てられます。 以下のSQLではYEARを指定しているため年のみが残り月と日は01に、時分秒は00に切り捨てられています。 SELECT user_id, time, DATE_TRUNC('YEAR', CAST(time AS TIMESTAMP)) AS month_begin FROM athena_workshop.login LIMIT 10 月初の日付の算出 Pandasで言うところのMonthBeginクラスで扱うような、月初を得たりは結構お仕事で必要になったりします。そういった場合は前述のDATE_TRUNC関数で'MONTH'の単位を指定することで月初が得られます。 SELECT date, sales, DATE_TRUNC('MONTH', CAST(date AS DATE)) AS month_begin FROM athena_workshop.total_sales_daily LIMIT 10 Pythonでの書き方 PandasにMonthBeginクラスがあるのでそちらを使えます。使い方はDayなどの他のクラスと同様です。引数に整数を指定した場合はその月数分で計算がされます。 ただし注意しない点として、対象が既に月初の日付になっている場合前月の月初になってしまいます。例えば2021-01-01の日付の行に対して計算がされると2020-12-01といったように前月の日付になってしまいます。一方で月初以外の2021-01-05などの日付は2021-01-01の日付になります。 2021-01-01の日付指定時 import pandas as pd from pandas.tseries.offsets import MonthBegin df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] -= MonthBegin() print(df) date sales 0 2020-12-01 768500 2021-01-05の日付指定時 import pandas as pd from pandas.tseries.offsets import MonthBegin df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-05/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] -= MonthBegin() print(df) date sales 0 2021-01-01 687100 1つの解決策として、一度MonthBeginで加算して翌月にしてからMonthBeginで1か月分減算するという方法があります。これなら元の日付が月初かどうかに関係なくその月の月初になります。 import pandas as pd from pandas.tseries.offsets import MonthBegin df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += MonthBegin() df['date'] -= MonthBegin() print(df) date sales 0 2021-01-01 768500 月末の日付の算出 月初と同様に月末の日付の算出なども必要になることが結構あります。HIVEとかだとそれ専用の関数なども用意されていますしPandasだとMonthEndクラスなどがありますが、Athena(Presto)ではこの記事を書いている時点では(UDFなどを除いて)対応するものが無い?感じがしますので少しステップを踏む必要があります。 色々やり方はあると思いますが、今回は以下のようにして月末を計算しています。 月初を計算します(DATE_TRUNC('MONTH', ...))。 翌月の月初に変換します(+ INTERVAL '1' MONTH)。 翌月月初の前日を取得します(- INTERVAL '1' DAY as month_end)。 SELECT date, sales, DATE_TRUNC('MONTH', CAST(date AS DATE)) + INTERVAL '1' MONTH - INTERVAL '1' DAY as month_end FROM athena_workshop.total_sales_daily LIMIT 10 Pythonでの書き方 PandasにMonthEndクラスなどがありそちらで計算すると月末の日付が取れます。ただし月初のMonthBeginと同様に、対象が既に月末の日付の場合翌月の月末になってしまいます。 2021-01-31の月末の日付に対してMonthEndを加算している例 import pandas as pd from pandas.tseries.offsets import MonthEnd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-31/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += MonthEnd() print(df) date sales 0 2021-02-28 747700 その月の月末などに統一したい場合の解決策の一つとしては、一度MonthBeginを加算して翌月月初にしてからDayもしくはMonthEndを減算するという方法が取れます。 import pandas as pd from pandas.tseries.offsets import MonthBegin, MonthEnd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/total_sales_daily/dt%3D2021-01-31/data.json.gz?raw=true', lines=True, compression='gzip') df['date'] = pd.to_datetime(df['date']) df['date'] += MonthBegin() df['date'] -= MonthEnd() print(df) date sales 0 2021-01-31 747700 WEEKの単位について試す DATE_TRUNCのWEEKについて少し実際に動かして詳細を確認してみます。ドキュメントを読んでいた感じ、月曜に変換されるようです。 SELECT date, sales, DATE_TRUNC('WEEK', CAST(date AS DATE)) AS week_start FROM athena_workshop.total_sales_daily LIMIT 50 以下のように変換がされていることが分かります。 2021-01-01(金) -> 2020-12-28(月) 2021-03-02(火) -> 2021-03-01(月) ... 2021-02-22(月) -> 2021-02-22(月) 確かに月曜に変換されています。 QUARTERの単位について試す QUARTER(四半期)に関しても同様に少し実行してみます。 SELECT date, sales, DATE_TRUNC('QUARTER', CAST(date AS DATE)) AS quarter_start FROM athena_workshop.total_sales_daily LIMIT 10 各日付が2021-01-01の日付になっていることが分かります。ドキュメントではサンプルが2000-07-01という結果になっているので、以下のように変換される感じだと思われます。 1月月初から3月月末 -> 1月の月初 4月月初から6月月末 -> 4月の月初 7月月初から9月月末 -> 7月の月初 10月月初から12月月末 -> 10月の月初 DATE_ADDでもINTERVALと同じようなことができる INTERVALの方を使えばこちらはあまり使う機会が無さそう・・・な気もしますが、DATE_ADD関数でも似たような制御を行えます。 SELECT date, sales, DATE_ADD('DAY', 1, CAST(date AS date)) AS future_date FROM athena_workshop.total_sales_daily LIMIT 10 第一引数に単位、第二引数に値(DAYを指定した場合は日数)、第三引数に日付のカラムなどを指定します。単位の部分にはINTERVALと同様の値が指定できます。たとえばDAYの代わりにYEARを指定すれば年単位で加算などがされます。 SELECT date, sales, DATE_ADD('YEAR', 1, CAST(date AS date)) AS future_date FROM athena_workshop.total_sales_daily LIMIT 10 第二引数には負の値も指定できます。負の値を指定すると減算の挙動になります。 SELECT date, sales, DATE_ADD('YEAR', -1, CAST(date AS date)) AS future_date FROM athena_workshop.total_sales_daily LIMIT 10 現在の時間を取得する: CURRENT_TIME カラムの指定部分などにCURRENT_TIMEと指定すると現在の時間が取れます。後述するCURRENT_TIMESTAMPやCURRENT_DATEなども同じなのですが、関数と違って()の括弧が不要な点にはご注意ください。 SELECT date, sales, CURRENT_TIME FROM athena_workshop.total_sales_daily LIMIT 10 他の関数などと同様にASなども指定できます(以下のサンプルではcurrent_timeは予約語となりASで使えないのでcurrent_time_とサフィックスにアンダースコアを付けています)。 SELECT date, sales, CURRENT_TIME AS current_time_ FROM athena_workshop.total_sales_daily LIMIT 10 現在の日時を取得する: CURRENT_TIMESTAMPとNOW CURRENT_TIMEの代わりにCURRENT_TIMESTAMPを指定すると日付付きで現在の日時が取得できます。 SELECT date, sales, CURRENT_TIMESTAMP FROM athena_workshop.total_sales_daily LIMIT 10 ちなみにこのCURRENT_TIMESTAMPの代わりにNOW関数も使うことができます。挙動は同じですが記述が短くなります。 SELECT date, sales, NOW() AS now FROM athena_workshop.total_sales_daily LIMIT 10 Pythonでの書き方 ビルトインのdatetimeのnow関数で現在日時を取れます。 from datetime import datetime import pandas as pd df: pd.DataFrame = pd.DataFrame(data={'a': [1, 2]}) df['now'] = datetime.now() print(df) a now 0 1 2021-07-13 08:49:58.254533 1 2 2021-07-13 08:49:58.254533 データフレームに設定すると値はPandasのTimestampとなります。そちらはdatetime型と同じようにstrftimeメソッドが使えるので%H:%M:%Sなどの任意のフォーマットを指定すれば時間部分などのみ取得できます。 from datetime import datetime import pandas as pd df: pd.DataFrame = pd.DataFrame(data={'a': [1, 2]}) df['now'] = datetime.now() timestamp: pd.Timestamp = df.at[0, 'now'] print(timestamp.strftime('%H:%M:%S')) 08:53:11 現在の日付を取得する: CURRENT_DATE CURRENT_DATEを指定すると現在の日付(時間は含みません)を取得できます。 SELECT date, sales, CURRENT_DATE FROM athena_workshop.total_sales_daily LIMIT 10 ただし、この値後述するタイムゾーン関係がエラーとなり設定できません(?)。日本環境で扱うと場合によっては日付がずれうるので注意が必要かもしれません(後でもう少し試してみようと思いますが、タイムゾーン付きでCURRENT_TIMESTAMPなどで算出してから日付部分を抽出した方が無難な気もちょっとしています)。 タイムゾーンの設定 前節のCURRENT_TIMEではUTCのタイムゾーンが設定されていました。ただし日本でお仕事する上では日本のタイムゾーンの値が取りたくなることが多くなると思います。 そういった場合にはAT TIME ZONE 'Asia/Tokyo'などと設定すると日本の時間となります。 SELECT date, sales, CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo' AS current_time_ FROM athena_workshop.total_sales_daily LIMIT 10 日付へのキャストのエイリアス(短縮系の記述) 前節で触れてきたように、文字列などを日付にキャスト(型変換)したい場合にはCAST(<対象のカラム> AS DATE)といった記述が必要になりました。 Athena(Presto)ではこのキャストの記述を短くするためのDATE(<対象のカラム>)という関数が別途用意されています。こちらを使うとCASTの記述を省略できます(挙動はDATEへCASTした時と同じものになります)。 SELECT date AS date_str, DATE(date) AS date, sales FROM athena_workshop.total_sales_daily LIMIT 10 UNIXTIMEを日時に変換する ※この節以降、login_unixtimeというログの日時がUNIXTIMEになっているテーブルを利用します。loginテーブルと同様に、ユーザーのログイン時間を扱う想定のデータとしてあります。 unixtimeカラムは整数の型で以下のようなデータになっています。 SELECT * FROM athena_workshop.login_unixtime LIMIT 10; たまにログでUNIXTIMを扱う必要が出で来るときがあります。そういった場合にはFROM_UNIXTIME関数でUNIXTIMEから通常の日時に変換することができます。第一引数に変換したいカラム名(今回はunixtimeカラム)などを指定します。 SELECT user_id, unixtime, FROM_UNIXTIME(unixtime) AS time FROM athena_workshop.login_unixtime LIMIT 10; ただしこの値はタイムゾーンが加味されていません。日本時間で扱いたい場合には他と同様にAT TIME ZONE 'Asia/Tokyo'とタイムゾーンの指定を追加する必要があります。 SELECT user_id, unixtime, FROM_UNIXTIME(unixtime) AT TIME ZONE 'Asia/Tokyo' AS time FROM athena_workshop.login_unixtime LIMIT 10; Pythonでの書き方 Pandasだとまずはdt.tz_localize('UTC')でUTCに変換してからさらに特定のタイムゾーン(今回は日本時間が欲しいのでAsia/Tokyoをdt.tz_convert)を指定する必要があるようです。少し煩雑には感じます。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login_unixtime/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['time'] = pd.to_datetime(df['unixtime'], unit='s') df['time'] = df['time'].dt.tz_localize('UTC').dt.tz_convert('Asia/Tokyo') print(df.head()) user_id unixtime device_type time 0 17447 1609426803 2 2021-01-01 00:00:03+09:00 1 10800 1609426805 1 2021-01-01 00:00:05+09:00 2 15188 1609426807 1 2021-01-01 00:00:07+09:00 3 18304 1609426807 1 2021-01-01 00:00:07+09:00 4 22496 1609426810 2 2021-01-01 00:00:10+09:00 日本時間に変換した後にタイムゾーン部分の表記を消したい場合にはdt.tz_localize(None)とさらに繋げると対応できるようです。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login_unixtime/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['time'] = pd.to_datetime(df['unixtime'], unit='s') df['time'] = df['time'].dt.tz_localize('UTC').dt.tz_convert('Asia/Tokyo') df['time'] = df['time'].dt.tz_localize(None) print(df.head()) user_id unixtime device_type time 0 17447 1609426803 2 2021-01-01 00:00:03 1 10800 1609426805 1 2021-01-01 00:00:05 2 15188 1609426807 1 2021-01-01 00:00:07 3 18304 1609426807 1 2021-01-01 00:00:07 4 22496 1609426810 2 2021-01-01 00:00:10 日時からUNIXTIMEに変換する 逆に日時からUNIXTIMEにしたい場合にはTO_UNIXTIME関数を使います。第一引数に対象の時間のカラムなどを指定します。 ただし元の日時などが日本時間の場合にはこの関数でも時差を加味する必要があります。 関数自体には時差関係の引数などは無いようで、且つAT TIME ZONEなどで調整していてもちょっとうまくいかなかったので以下のSQLでは普通に日本時間を想定して9時間減算しています。 SELECT user_id, time, TO_UNIXTIME(CAST(time AS TIMESTAMP) - INTERVAL '9' HOUR) AS unixtime FROM athena_workshop.login LIMIT 10 結果は浮動小数点数で返ってくるので指数表記になっています(例 : 1.613919601E9)。整数の形式のものが必要なケースも多いでしょうから、そういった場合にはCAST(... AS BIGINT)と指定して整数変換(キャスト)すると必要な値が取れます。 SELECT user_id, time, CAST(TO_UNIXTIME(CAST(time AS TIMESTAMP) - INTERVAL '9' HOUR) AS BIGINT) AS unixtime FROM athena_workshop.login LIMIT 10 Pythonでの書き方 以下の手順で対応します。 pd.to_datetimeでまずは文字列から日時の型(Timestamp)に変換。 .dt.tz_localize('Asia/Tokyo')でタイムゾーンを日本時間のものに変換。 タイムゾーン変換結果は2021-01-01 00:00:01+09:00といったような値になるので、これをastype(int)でキャストするとナノ秒まで含めた1609426801000000000といった値になるので、それを// 10 ** 9と除算することで結果の整数を取得。 としています(//は切り捨て除算なので整数になります)。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['timestamp'] = pd.to_datetime(df['time']) df['timestamp'] = df['timestamp'].dt.tz_localize('Asia/Tokyo') df['timestamp'] = df['timestamp'].astype(int) // 10 ** 9 print(df.head()) user_id time device_type timestamp 0 8590 2021-01-01 00:00:01 1 1609426801 1 568 2021-01-01 00:00:02 1 1609426802 2 17543 2021-01-01 00:00:04 2 1609426804 3 15924 2021-01-01 00:00:07 1 1609426807 4 5243 2021-01-01 00:00:09 2 1609426809 特定単位の時間(間隔値)への変換処理 Athena(Presto)にはPARSE_DURATIONという関数があります。第一引数に特定の単位付きの文字列を指定すると特殊な型(interval day to second)の時間の値が返ってきます。例えばminuteを表すmと一緒に10mという文字列を引数に指定すると10分のinterval day to secondの時間が返ってきます。0 00:10:00.000といったような特殊な表記になります。 SELECT user_id, time, PARSE_DURATION('10m') AS ten_minutes FROM athena_workshop.login LIMIT 10 Prestoのドキュメントにはあるものの、他の記事とかはほとんどこの関数関係がヒットせずに「何に使うのだろう?」と少し考えていたのですが、1つの使い道として浮動小数点数もサポートされているという点がメリットかなと思いました。 例えば10.5mといった用に書くと10分と30秒といった値が返ってきます。 SELECT user_id, time, PARSE_DURATION('10.5m') AS minute_val FROM athena_workshop.login LIMIT 10 この値はTO_MILLISECONDS関数でミリ秒変換できます。/ 1000で除算すれば秒単位の値にできます。 SELECT user_id, time, TO_MILLISECONDS(PARSE_DURATION('10.5m')) / 1000 AS second_val FROM athena_workshop.login LIMIT 10 この値をDATE_ADD関数に指定すれば10.5分後みたいな制御ができるようになります(INTERVALにはこの値は直接指定するとエラーになるようで、DATE_ADD関数の方を使いました。また、INTERVALの記述の場合は浮動小数点数を指定するとエラーになります)。 SELECT user_id, time, DATE_ADD('SECOND', TO_MILLISECONDS(PARSE_DURATION('10.5m')) / 1000, CAST(time AS TIMESTAMP)) AS future_time FROM athena_workshop.login LIMIT 10 10.5分後みたいなシンプルなケースであればINTERVALの記述を2回するなり、630秒後とかで計算してもシンプルに対応できますが、計算が面倒な場合やもっと直観的ではない値(例えば10.3日など)の場合の計算はこういった浮動小数点数付きの表記の方が可読性が高くなる(意図が伝わりやすくなる)ケースがあるかもしれません。 分のm以外にも以下のような色々な単位がサポートされています。 単位 内容 ns ナノ秒(1 / 1000 / 1000 / 1000秒) us マイクロ秒(1 / 1000 / 1000秒) ms ミリ秒(1 / 1000秒) s 秒 m 分 h 時 d 日 MySQL互換の日時のフォーマット処理 普段この辺りは仕事だとPython上で扱うケースが大半なのでMySQL上でほぼ使ったことはないのですが、任意のフォーマットの文字列 → 時間の変換やその逆の時間 → 任意のフォーマットの文字列に変換する関数が存在します。Pythonだとstrftimeとかstrptimeと同じようなものに該当します。 strftime的に日時 → 任意のフォーマットの文字列に変換したい場合にはDATE_FORMAT関数を使います。strptime的に任意のフォーマットの文字列 → 日時に変換したい場合にはDATE_PARSE関数を使います。それぞれの関数やサポートされているフォーマットの指定などは以降の節で順番に触れていきます。 日時から任意のフォーマットの文字列を生成する: DATE_FORMAT DATE_FORMAT関数を使うと日時のカラムなどを特定のフォーマットの文字列に変換できます。第一引数に日時などのカラム、第二引数にはフォーマットの文字列を指定します。 第二引数のフォーマットに関しては後々の節で詳しく触れます。以下のSQLでは'%m/%d'というフォーマットを指定しています。%mが2文字の月の文字列、%dが2文字の日の文字列なので'%m/%d'で01/05みたいな表記になります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%m/%d') FROM athena_workshop.login LIMIT 10 Pythonでの書き方 Pandasのシリーズのdt属性でビルトインのdatetimeみたいなインターフェイスにアクセスできるようになっているのでそちらのstrftimeメソッドで対応ができます。今回の例ではフォーマットの文字列も一致していますが、他のフォーマットは一致していないものも多いのでご注意ください。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['time'] = pd.to_datetime(df['time']) df['formatted_time'] = df['time'].dt.strftime('%m/%d') print(df.head()) user_id time device_type formatted_time 0 8590 2021-01-01 00:00:01 1 01/01 1 568 2021-01-01 00:00:02 1 01/01 2 17543 2021-01-01 00:00:04 2 01/01 3 15924 2021-01-01 00:00:07 1 01/01 4 5243 2021-01-01 00:00:09 2 01/01 任意の日時のフォーマットの文字列から日時を生成する: DATE_PARSE ※この節から説明のためにno_symbol_loginという名前のテーブルを扱っていきます。日時のtimeカラムがハイフンやコロンなどの記号を含まない形で保存されているログインデータのテーブルとなります。 SQLで確認してみると以下のようになっています。 SELECT * FROM athena_workshop.no_symbol_login LIMIT 10; 今度は逆に特定のフォーマットの文字列から日時(TIMESTAMP)を取得する処理について触れていきます。たとえば20210101103050みたいにハイフンなどの記号が省かれているデータがあるかもしれませんし、もしくは2021年01月01日10時30分50秒みたいな日本語表記になっているデータもあるかもしれません。そういった特殊なフォーマットの場合に便利です。 DATE_PARSE関数を使います。第一引数に該当のフォーマットの文字列のカラムなどを指定し、第二引数に想定されるフォーマットを指定します。 今回のSQL例では20210101103050といったように記号やスペースなどを含まない年月日時分秒のフォーマットの文字列を日時に変更します。フォーマットは%Y%m%d%H%i%Sとなります。 SELECT user_id, time, DATE_PARSE(time, '%Y%m%d%H%i%S') AS parsed_time FROM athena_workshop.no_symbol_login LIMIT 10; Pythonでの書き方 Pandas側ではpd.to_datetime関数を使う際にformat引数でフォーマットを指定する必要があります。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/no_symbol_login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['parsed_time'] = pd.to_datetime(df['time'], format='%Y%m%d%H%M%S') print(df.head()) user_id time device_type parsed_time 0 9093 20210101000000 1 2021-01-01 00:00:00 1 23210 20210101000007 1 2021-01-01 00:00:07 2 9560 20210101000027 2 2021-01-01 00:00:27 3 23197 20210101000028 2 2021-01-01 00:00:28 4 2569 20210101000032 1 2021-01-01 00:00:32 日時の各フォーマット種別 以降の節ではフォーマットの各文字列の使い方や主だったもののフォーマットの定義について触れていきます。 ※全てのフォーマットは触れません。また、Prestoのドキュメントには載っているフォーマットでもAthena上で利用しようとするとエラーになるものもあるようです(Athena側で裏側で使われているPrestoのバージョンが最新のドキュメントと比べて少し古い・・・とかでしょうか?)。 フォーマットの指定の基本的な使い方 各フォーマット(年や月など)を表す部分には半角の%の記号が付けられます。 フォーマットの部分はシングルクォーテーションで囲む必要があります(例 : '%d')。 複数のフォーマットの文字列を組み合わせて使うことができます(例 : %m%d)。 任意の文字や記号をフォーマットの文字列の間に挟むことができます(例 : %m月%d日, %m-%d)。 4桁の西暦のフォーマット: %Y %Yのフォーマットで4桁の西暦の年が取れます(例 : 2021)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%Y') AS year FROM athena_workshop.login LIMIT 10 2桁の西暦のフォーマット: %y %yのフォーマットで下2桁の西暦の年が取れます(例 : 21)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%y') AS year FROM athena_workshop.login LIMIT 10 ゼロ埋めされた形の月のフォーマット: %m %mのフォーマットで左にゼロ埋めがされた状態の月の値が取れます。結果はゼロ埋めされて必ず2桁となります(例 : 03)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%m') AS month FROM athena_workshop.login LIMIT 10 ゼロ埋めしない形の月のフォーマット: %c %cのフォーマットでゼロ埋めがされていない状態の月の値が取れます(例 : 3)。結果は月に応じて1桁もしくは2桁になります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%c') AS month FROM athena_workshop.login LIMIT 10 月の文字列のフォーマット: %M 英語表記となりますが、%Mの表記で月のラベルを取得することができます(例 : February)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%M') AS month FROM athena_workshop.login LIMIT 10 ゼロ埋めされた形の日のフォーマット: %d %dのフォーマットでゼロ埋めされた状態の日が取れます(例 : 05)。結果は必ず2桁となります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%d') AS day FROM athena_workshop.login LIMIT 10 ゼロ埋めしない形の日のフォーマット: %e %eのフォーマットでゼロ埋めされていない形の日の値が取れます(例 : 6)。結果は日付に応じて1桁もしくは2桁となります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%e') AS day FROM athena_workshop.login LIMIT 10 曜日の英語の文字列のフォーマット: %aと%W %aと%Wのフォーマットで曜日の英語のラベルを取ることができます。%aの方は短い表記となります(例 : Mon)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%a') AS weekday FROM athena_workshop.login LIMIT 10 %Wの方は長い表記となります(例 : Wednesday)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%W') AS weekday FROM athena_workshop.login LIMIT 10 ゼロ埋めされた形の時のフォーマット: %H %Hのフォーマットでゼロ埋めされた時の値が取れます(例 : 05)。結果は必ず2桁となります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%H') AS hour FROM athena_workshop.login WHERE time >= '2021-03-05 05:30:00' AND dt = '2021-03-05' LIMIT 10 ゼロ埋めされていない形の時のフォーマット: %k %kのフォーマットを使うとゼロ埋めされていない時のフォーマットの値が取れます(例 : 5)。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%k') AS hour FROM athena_workshop.login WHERE time >= '2021-03-05 05:30:00' AND dt = '2021-03-05' LIMIT 10 午前か午後かの英語表記のフォーマット: %p %pというフォーマットを使うと午前か午後かに応じてAMもしくはPMという文字列が取得できます。 午前に結果がなるケース例 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%p') AS am_or_pm FROM athena_workshop.login WHERE time >= '2021-03-05 05:30:00' AND dt = '2021-03-05' LIMIT 10 午後に結果がなるケース例 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%p') AS am_or_pm FROM athena_workshop.login WHERE time >= '2021-03-05 15:30:00' AND dt = '2021-03-05' LIMIT 10 午前か午後かの値が続く時分秒のフォーマット: %r %rのフォーマットを使うとAMもしくはPMが付く形で時分秒の値が取れます。24時間表記ではなくAMとPMごとの12時間表記となります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%r') AS time FROM athena_workshop.login WHERE time >= '2021-03-05 15:30:00' AND dt = '2021-03-05' LIMIT 10 24時間表記での時分秒のフォーマット: %T %Tのフォーマットでは、AMやPMなどの表記はなく24時間表記での時間が取れます。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%T') AS time FROM athena_workshop.login WHERE time >= '2021-03-05 15:30:00' AND dt = '2021-03-05' LIMIT 10 ゼロ埋めされた形の分のフォーマット: %i %iのフォーマットでゼロ埋めされた分の値が取れます(00~59)。結果は必ず2桁となります。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%i') AS minute FROM athena_workshop.login WHERE time >= '2021-03-05 15:05:00' AND dt = '2021-03-05' LIMIT 10 秒とかもそうなのですが、ゼロ埋めされない形の分や秒のフォーマットはドキュメントに見当たらない?ようです。 ゼロ埋めされた形の秒のフォーマット: %sと%S %sと%Sのフォーマットでゼロ埋めされた秒の値が取れます。大文字小文字両方同じ結果になるようです。 小文字のsを使った場合 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%s') AS second FROM athena_workshop.login WHERE time >= '2021-03-05 15:05:00' AND dt = '2021-03-05' LIMIT 10 大文字のSを使った場合 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%S') AS second FROM athena_workshop.login WHERE time >= '2021-03-05 15:05:00' AND dt = '2021-03-05' LIMIT 10 1年のうちで何日目なのかのフォーマット: %j %jのフォーマットで1年で何日目に該当するのかの日数が取れます。三桁分にゼロ埋めされる形で返ってくるようです。 SELECT user_id, time, DATE_FORMAT(CAST(time AS TIMESTAMP), '%j') AS second FROM athena_workshop.login WHERE time >= '2021-01-05 15:05:00' AND dt = '2021-01-05' LIMIT 10 日時の部分的な抽出処理を楽にする関数集 Athena(Presto)には特定部分の日時を抽出するのに便利な関数が色々用意されています。これらで完結するならこちらを使うとSQLの記述がシンプルになるケースがあります。 以降の節ではそれらの関数についてそれぞれ触れていきます。各関数の引数には日時(TIMESTAMP)に型変換(キャスト)したカラムなどが必要になります。また、各関数の結果はゼロ埋めなどはされず整数などで返ってきます。 YEAR関数 YEAR関数では年部分を抽出することができます。 SELECT user_id, time, YEAR(CAST(time AS TIMESTAMP)) AS year FROM athena_workshop.login WHERE time >= '2021-01-05 15:05:00' AND dt = '2021-01-05' LIMIT 10 QUARTER関数 QUARTERでは各日時が第何四半期に該当するのかの値が取れます。値は1からスタートします(1~4の値となります)。 SELECT user_id, time, QUARTER(CAST(time AS TIMESTAMP)) AS quarter FROM athena_workshop.login WHERE time >= '2021-01-05 15:05:00' AND dt = '2021-01-05' LIMIT 10 MONTH関数 MONTH関数では月部分の整数が取れます。 SELECT user_id, time, MONTH(CAST(time AS TIMESTAMP)) AS month FROM athena_workshop.login WHERE time >= '2021-01-05 15:05:00' AND dt = '2021-01-05' LIMIT 10 WEEK関数 WEEK関数では該当の日時が1年の中で第何週に該当するのかの値が取れます。 SELECT user_id, time, WEEK(CAST(time AS TIMESTAMP)) AS week FROM athena_workshop.login WHERE time >= '2021-02-10 15:05:00' AND dt = '2021-02-10' LIMIT 10 DAY関数 DAY関数では該当の日時の日部分が整数で返ってきます。 SELECT user_id, time, DAY(CAST(time AS TIMESTAMP)) AS day FROM athena_workshop.login WHERE time >= '2021-02-05 15:05:00' AND dt = '2021-02-05' LIMIT 10 DAY_OF_WEEKとDOW関数 DAY_OF_WEEK関数では該当の日時の曜日の数値が返ってきます。月曜が1で日曜が7となります。 SELECT user_id, time, DAY_OF_WEEK(CAST(time AS TIMESTAMP)) AS day_of_week FROM athena_workshop.login WHERE time >= '2021-02-05 15:05:00' AND dt = '2021-02-05' LIMIT 10 DOW関数はDAY_OF_WEEKの短縮系となります。こちらも同じ挙動をします。 SELECT user_id, time, DOW(CAST(time AS TIMESTAMP)) AS day_of_week FROM athena_workshop.login WHERE time >= '2021-02-05 15:05:00' AND dt = '2021-02-05' LIMIT 10 DAY_OF_YEARとDOY関数 DAY_OF_YEAR関数では1年の中で何日目に該当するのかの値を取得できます。 SELECT user_id, time, DAY_OF_YEAR(CAST(time AS TIMESTAMP)) AS day_of_year FROM athena_workshop.login WHERE time >= '2021-02-05 15:05:00' AND dt = '2021-02-05' LIMIT 10 DOY関数はDAY_OF_YEAR関数の短縮系です。こちらも同じ挙動をします。 SELECT user_id, time, DOY(CAST(time AS TIMESTAMP)) AS day_of_year FROM athena_workshop.login WHERE time >= '2021-02-05 15:05:00' AND dt = '2021-02-05' LIMIT 10 HOUR関数 HOUR関数では時間が取れます(0~23)。 SELECT user_id, time, HOUR(CAST(time AS TIMESTAMP)) AS hour FROM athena_workshop.login WHERE time >= '2021-02-05 05:05:00' AND dt = '2021-02-05' LIMIT 10 MINUTE関数 MINUTE関数では分の値が取れます(0~59)。 SELECT user_id, time, MINUTE(CAST(time AS TIMESTAMP)) AS minute FROM athena_workshop.login WHERE time >= '2021-02-05 05:08:00' AND dt = '2021-02-05' LIMIT 10 SECOND関数 SECOND関数では秒の値が取れます(0~59)。 SELECT user_id, time, SECOND(CAST(time AS TIMESTAMP)) AS second FROM athena_workshop.login WHERE time >= '2021-02-05 05:00:00' AND dt = '2021-02-05' LIMIT 10 参考文献・参考サイトまとめ The Applied SQL Data Analytics Workshop: Develop your practical skills and prepare to become a professional data analyst, 2nd Edition HAVINGの使い方 【SQL入門】 サブクエリを使った検索条件の設定 WHERE句のサブクエリは大抵の場合テーブルJOINで代替できる SQL関数coalesceの使い方と読み方 coalesceとは pandas get the row-wise minimum value of two or more columns Comparison Functions and Operators Mathematical Functions and Operators Add months to a date in Pandas pandasで文字列にスライスを適用して任意の位置・長さの部分を抽出 String Functions and Operators pandas.DataFrameの複数の列の文字列を結合して新たな列を生成 Treasure Data(Presto/Hive)で月の最終日を取得する方法 Date and Time Functions and Operators athena(presto)で、dateとtimeからJSTを得る using time zone in pandas to_datetime DataFrame からタイムゾーンを削除したいがうまくいかない pandasでunixtimeへの変換 prestoの日付関数 pandasデータフレームの日付型⇄文字型変換とdfply使用時のメモ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1. Pythonで学ぶ統計学 2-4. 統計用語の基本

統計学の用語には文字面の似通ったものが多く紛らわしいので、ここで改めて、特に平均と分散に関する主な用語についてまとめます 基本的な統計量 種別 用語 対象 記号・表記 略意 平均 ➀平均値 資料 $\overline{x}$ 資料の平均値 ➁標本平均 標本 $\overline{X} \hspace{5px}$, $M$ 標本の平均値 ➂母平均 母集団 $μ$ 母集団の平均値 ➃期待値 確率分布 $μ$, $E(X)$ 予測値としての平均値 分散 ➄母分散 母集団 $σ^2$ 母平均への集中度 ➅標本分散 標本 $s^2$ 標本平均への集中度 ➆確率変数の分散 確率分布 $σ^2$, $V(X)$ 期待値への集中度 ➀平均値(mean) 一定の属性をもつ数値データ群の平均値 データの「ちらばりの重心」を示し、下式で定義されるとおりデータの総和をデータ数で割ったもの $ \displaystyle \overline{x} = \frac{1}{N} \sum_{i=1}^{N} x_{i} = \frac{x_{1} + x_{2} +…+ x_{N}}{N} $ 都内のある高校で3年生のクラスの男子生徒30名の身長を測定したと仮定して乱数を生成し、そのヒストグラムと平均値を以下に示します ➁標本平均(sample mean) 母集団から無作為抽出された標本データの平均値を「標本平均」といいます 前項の平均値と混同されやすいので要注意です $ \displaystyle \overline{X} = \frac{1}{n} \sum_{i=1}^{n} x_{i} = \frac{X_{1} + X_{2} +…+ X_{n}}{n} $ 都内の高校3年生の男子生徒を母集団として、無作為に30人を抽出したと仮定して乱数を生成し、そのヒストグラムと平均値(標本平均)を以下に示します 先の「平均値」とは異なり、こちらは都内の高3男子生徒の全体(母集団)の縮図となっています さらに、標本抽出(サンプリング)を700回くり返したと仮定し、そこで得られた700通りの標本平均の分布とそれらの平均値を以下に示します 標本平均は確率変数なので、その分布は「確率分布」であり、正規分布になります ➂母平均(population mean) 母集団の平均値を「母平均」といいます 母集団は集計分析の対象となる集合全体のことで、標本を抽出するときのもとの集団にあたります 真に知りたいのはこの母平均なわけですが、これがわからないので「標本平均」で代用します 従って、標本数が増えると、標本平均は少しずつ「母平均」に近づいていくことになります $ \displaystyle μ = \frac{1}{N} \sum_{i=1}^{N} x_{i} = \frac{x_{1} + x_{2} +…+ x_{N}}{N} $ 都内の高校3年生の男子生徒は50,440人なので、その全員の身長を測定したと仮定して乱数を生成し、その分布と平均値(母平均)を以下に示します ➃期待値(expectation) 期待値と平均値はしばしば混用されますが、実際まだ手に入れていない未知のデータの平均値(予想値)を「期待値」といいます 確率変数はいろいろの値をとり得るわけですが、それらを代表する平均値のことであって、正確には「確率の重みつき平均」です 個人的な信念や勘などに依拠しない客観的な予測値ともいえます $離散型確率変数: \displaystyle E(X) = \sum_{i=1}^{n} x_{i} p_{i} $ $連続型確率変数: \displaystyle E(X) = \int_{-\infty}^{\infty} xf(x)dx $ 先の標本 $n=30$ における平均(標本平均=期待値)および分散を用いた確率密度関数を以下に示します 定義上、標本においては平均値と期待値はまったく同じになります 統計とは半面、既知の一部から未知の全体を推定する取り組みといえます ➄母分散(population variance) 母集団の分散 分散は、データが平均値を中心としてどれくらい散らばっているかを表す指標です $ \displaystyle σ^2 = \frac{1}{n} \sum_{i=1}^{n} (x_{i} - μ)^2 = \frac{(x_{1} - μ)^2 + (x_{2} - μ)^2 +…+ (x_{n} - μ)^2}{n} $ 母分散は上式に定義されますが、そもそも母集団は未知のデータですから、母分散 $ σ^2 $ は標本分散 $ s^2 $ に近似されるという想定のもとに、標本分散で母分散を推定しますが・・・ 「都内の高校3年生の男子全体」という母集団を仮定し、母分散$σ^2$及びその平方根にあたる母標準偏差$σ$を求めてみましょう # 母集団を仮定し、母分散を取得 np.random.seed(seed=0) # 乱数を固定 population = np.random.normal(loc = 170, scale = 10, size = 50440) # 平均μ=170, 標準偏差σ=10 # 母平均 population_av = np.mean(population) print("母平均:", population_av) # 母標準偏差 population_std = np.std(population, ddof=0) # 標準偏差 print("母標準偏差:", population_std) # 母分散 print("母分散:", population_std ** 2) ➅標本分散(sample variance) 標本分散は、標本データが標本平均(期待値)を中心にどれくらい散らばっているかを表す指標です ただし、標本分散 $ S^2 $ の値は、母分散と比べて過小評価になるという偏りがありますので、これを修正した不偏分散(unbiased variance) $ s^2 $ を用います $不偏分散: \displaystyle s^2 = \frac{1}{n-1} \sum_{i=1}^{n} (x_{i} - μ)^2 = \frac{(x_{1} - \overline{x})^2 + (x_{2} - \overline{x})^2 +…+ (x_{n} - \overline{x})^2}{n-1} $ $標本分散: \displaystyle S^2 = \frac{1}{n} \sum_{i=1}^{n} (x_{i} - μ)^2 = \frac{(x_{1} - \overline{x})^2 + (x_{2} - \overline{x})^2 +…+ (x_{n} - \overline{x})^2}{n} $ 先に仮定した母集団から無作為に30人を抽出し、不偏標準偏差と標準偏差を取得してみます # 母集団から無作為に30人を抽出 import random random.seed(5) sample = [] for i in range(30): value = random.choice(population) sample.append(value) print(sample) print(len(sample)) # 標本平均 sample_av = np.mean(sample) print("標本平均:", sample_av) # 標本標準偏差 sample_std = np.std(sample, ddof=1) # 不偏標準偏差 print("不偏標準偏差:", sample_std) sample_std_unbiase = np.std(sample, ddof=0) # 標準偏差 print("( 標準偏差:", sample_std_unbiase,")") # 標本分散 print("標本分散:", sample_std ** 2) 不偏分散を採用する根拠についてはこちらへ→1. Pythonで学ぶ統計学 1-3. 各種統計量の計算(statistics) ➆確率変数の分散(variance) 確率分布が与えられたとき、下式で算出される値が「確率変数の分散$σ^2$」(標準偏差は$σ$)になります $離散型確率変数: \displaystyle V(X) = \sum_{i=1}^{n} (x_{i} - μ)^2 p_{i} \hspace{5px}$ $連続型確率変数: \displaystyle V(X) = \int_{-\infty}^{\infty} (x - μ)^2 f(x)dx $ 確率変数$X$が、期待値$μ$を中心として$μ-σ≦X≦μ+σ$の範囲内の値をとる確率は 68.3%(0.6827) です 同じく確率変数$X$が$μ-2σ≦X≦μ+2σ$の値をとる確率は 95.5%(0.9545) 同じく確率変数$X$が$μ-3σ≦X≦μ+3σ$の値をとる確率は 99.7%(0.9973) で、これを3シグマ範囲といいます この区間外に落ちる確率はほぼ3/1000ですから、つまり3シグマ範囲は事実上すべて(全体の確率=1)に相当します Appendix # 数値計算ライブラリ import numpy as np from scipy import stats # グラフ描画ライブラリ import matplotlib.pyplot as plt %matplotlib inline # matplotlibの日本語表示対応モジュール !pip install japanize-matplotlib import japanize_matplotlib ➀平均値(mean) # 都内のある高校で3年生のクラスの男子生徒30名の身長を測定したと仮定 # 任意の範囲で乱数を生成 np.random.seed(3) # 乱数を固定 height_data = np.random.randint(low = 160, high = 180, size = 30) # 平均値を取得 ave = np.mean(height_data) print("平均値:", ave) # グラフ描画 plt.hist(height_data, color = 'tab:cyan', rwidth = 0.9) plt.vlines(x=ave, ymin=0, ymax=6, color="orange", lw=4, linestyle=':') plt.text(ave, -1.2, '↑\n平均', fontsize = 13, horizontalalignment='center', color="darkorange") plt.show() ➁標本平均(sample mean) # 都内の高校3年生の男子生徒を母集団として、無作為に30人を抽出したと仮定 # 正規分布に従う乱数を生成 np.random.seed(4) # 乱数を固定 sample_data = np.random.normal(loc = 170, scale = 10, size = 30) # 平均μ=170, 標準偏差σ=10 # 平均値を取得 sample_mean = np.mean(sample_data) print("平均値:", sample_mean) # グラフ描画 plt.hist(sample_data, color = 'tab:cyan', rwidth = 0.9) # ヒストグラムを描画 plt.vlines(x=sample_mean, ymin=0, ymax=9, color="orange", lw=4, linestyle=':') # 平均の垂直線 plt.text(sample_mean, -2, '↑\n平均', fontsize = 13, horizontalalignment='center', color="darkorange") # テキストの挿入 plt.show() # 標本抽出(サンプリング)を700回くり返したと仮定 samples = [] sample_averages = [] for i in range(700): np.random.seed(i) # 乱数を固定 X = np.random.normal(loc = 170, scale = 10, size = 30) samples.append(X) average = np.mean(X) sample_averages.append(average) print(sample_averages) # 平均値を取得 ave = np.mean(sample_averages) print("平均値:", ave) # グラフ描画 plt.hist(sample_averages, color = 'tab:cyan', rwidth = 0.9) plt.vlines(x=ave, ymin=0, ymax=170, color="orange", lw=4, linestyle=':') plt.text(ave, -35, '↑\n平均', fontsize = 13, horizontalalignment='center', color="darkorange") plt.show() ➂母平均(population mean) # 都内の高校3年生の男子生徒50,440人全員の身長を測定したと仮定 # 正規分布に従う乱数を生成 np.random.seed(seed=5) # 乱数を固定 population = np.random.normal(loc = 170, scale = 10, size = 50440) # 平均μ=170, 標準偏差σ=10 # 平均値を取得 ave_p = np.mean(population) print("平均値:", ave_p) # グラフ描画 plt.hist(population, color = 'tab:cyan', rwidth = 0.9) plt.vlines(x=ave_p, ymin=0, ymax=16000, color="orange", lw=4, linestyle=':') plt.text(ave_p, -3300, '↑\n平均', fontsize = 13, horizontalalignment='center', color="darkorange") plt.show() ➃期待値(expectation) # 標本n=30における平均(標本平均=期待値)および分散を用いた確率密度関数 from scipy import stats # 期待値・標準偏差を指定 mu = np.mean(sample_data) sigma = np.std(sample_data, ddof=1) # 不偏標準偏差 print("期待値:", mu) print("不偏標準偏差:", sigma) # 等差数列を生成 arithmetic_progression = np.linspace(mu-25, mu+25, 100) # pdfで確率密度関数を生成 norm_pdf = stats.norm.pdf(x=arithmetic_progression, loc=mu, scale=sigma) # loc=期待値, sigma=標準偏差 norm_pdf_max = np.max(norm_pdf) # 確率密度の最大値 # グラフ描画 plt.plot(arithmetic_progression, norm_pdf, lw=6, color="tab:cyan") plt.vlines(x=mu, ymin=0.004, ymax=norm_pdf_max, color="orange", lw=4, linestyle=':') plt.text(mu, 0.001, '平均', fontsize = 13, horizontalalignment='center', color="darkorange") plt.show() ➆確率変数の分散(variance) # 所与の確率分布を仮定(1σ範囲) # 所与の期待値と分散 mu = 0 variance = 1 # 期待値と分散(標準偏差)をもとに確率密度関数を生成 data_2 = np.linspace(mu-4, mu+4, 100) sigma = np.sqrt(variance) norm_pdf = stats.norm.pdf(x = data_2, loc = mu, scale = sigma) # 確率密度を取得 norm_pdf_max = np.max(norm_pdf) # 確率密度の最大値 lower_1 = stats.norm.pdf(x=mu-sigma, loc=mu, scale=sigma) # 確率変数μ-σ upper_1 = stats.norm.pdf(x=mu+sigma, loc=mu, scale=sigma) # 確率変数μ+σ lower_2 = stats.norm.pdf(x=mu-2*sigma, loc=mu, scale=sigma) # 確率変数μ-2σ upper_2 = stats.norm.pdf(x=mu+2*sigma, loc=mu, scale=sigma) # 確率変数μ+2σ lower_3 = stats.norm.pdf(x=mu-3*sigma, loc=mu, scale=sigma) # 確率変数μ-3σ upper_3 = stats.norm.pdf(x=mu+3*sigma, loc=mu, scale=sigma) # 確率変数μ+3σ # 確率密度関数のグラフを描画 plt.plot(data_2, norm_pdf, color = "tab:cyan", lw = 4) # 垂直線 plt.vlines(x=mu, ymin=0, ymax=norm_pdf_max, color="orange", lw=1.5) # μ plt.vlines(mu-sigma, 0, lower_1, color="black", lw=0.8) # μ-σ plt.vlines(mu+sigma, 0, upper_1, color="black", lw=0.8) # μ+σ plt.vlines(mu-2*sigma, 0, lower_2, color="black", lw=1, linestyle=':') # μ-2σ plt.vlines(mu+2*sigma, 0, upper_2, color="black", lw=1, linestyle=':') # μ+2σ plt.vlines(mu-3*sigma, 0, lower_3, color="black", lw=1, linestyle=':') # μ-3σ plt.vlines(mu+3*sigma, 0, upper_3, color="black", lw=1, linestyle=':') # μ+3σ # テキストを配置 plt.text(mu-0.2, -0.03, 'μ', fontsize = 13, color = "darkorange") # μ plt.text(mu-sigma-0.4, -0.03, 'μ-σ', fontsize = 12) # μ-σ plt.text(mu+sigma-0.4, -0.03, 'μ+σ', fontsize = 12) # μ+σ plt.text(mu-2*sigma-0.4, -0.03, 'μ-2σ', fontsize = 10) # μ-2σ plt.text(mu+2*sigma-0.4, -0.03, 'μ+2σ', fontsize = 10) # μ+2σ plt.text(mu-3*sigma-0.4, -0.03, 'μ-3σ', fontsize = 10) # μ-3σ plt.text(mu+3*sigma-0.4, -0.03, 'μ+3σ', fontsize = 10) # μ+3σ plt.text(mu-0.5, 0.16, '68.3%', fontsize = 14) # 68.3% # 軸目盛を調整 plt.ylim(0, 0.45) # y軸目盛範囲 plt.xticks(color="None") # x軸ラベルを消去 plt.yticks(color="None") # y軸ラベルを消去 plt.show() # 所与の確率分布を仮定(2σ範囲) # 所与の期待値と分散 mu = 0 variance = 1 # 期待値と分散(標準偏差)をもとに確率密度関数を生成 data_2 = np.linspace(mu-4, mu+4, 100) sigma = np.sqrt(variance) norm_pdf = stats.norm.pdf(x = data_2, loc = mu, scale = sigma) # 確率密度を取得 norm_pdf_max = np.max(norm_pdf) # 確率密度の最大値 lower_1 = stats.norm.pdf(x=mu-sigma, loc=mu, scale=sigma) # 確率変数μ-σ upper_1 = stats.norm.pdf(x=mu+sigma, loc=mu, scale=sigma) # 確率変数μ+σ lower_2 = stats.norm.pdf(x=mu-2*sigma, loc=mu, scale=sigma) # 確率変数μ-2σ upper_2 = stats.norm.pdf(x=mu+2*sigma, loc=mu, scale=sigma) # 確率変数μ+2σ lower_3 = stats.norm.pdf(x=mu-3*sigma, loc=mu, scale=sigma) # 確率変数μ-3σ upper_3 = stats.norm.pdf(x=mu+3*sigma, loc=mu, scale=sigma) # 確率変数μ+3σ # 確率密度関数のグラフを描画 plt.plot(data_2, norm_pdf, color = "tab:cyan", lw = 4) # 垂直線 plt.vlines(x=mu, ymin=0, ymax=norm_pdf_max, color="orange", lw=1.5) # μ plt.vlines(mu-sigma, 0, lower_1, color="black", lw=1, linestyle=':') # μ-σ plt.vlines(mu+sigma, 0, upper_1, color="black", lw=1, linestyle=':') # μ+σ plt.vlines(mu-2*sigma, 0, lower_2, color="black", lw=0.8,) # μ-2σ plt.vlines(mu+2*sigma, 0, upper_2, color="black", lw=0.8) # μ+2σ plt.vlines(mu-3*sigma, 0, lower_3, color="black", lw=1, linestyle=':') # μ-3σ plt.vlines(mu+3*sigma, 0, upper_3, color="black", lw=1, linestyle=':') # μ+3σ # テキストを配置 plt.text(mu-0.2, -0.03, 'μ', fontsize = 13, color = "darkorange") # μ plt.text(mu-sigma-0.4, -0.03, 'μ-σ', fontsize = 10) # μ-σ plt.text(mu+sigma-0.4, -0.03, 'μ+σ', fontsize = 10) # μ+σ plt.text(mu-2*sigma-0.4, -0.03, 'μ-2σ', fontsize = 12) # μ-2σ plt.text(mu+2*sigma-0.4, -0.03, 'μ+2σ', fontsize = 12) # μ+2σ plt.text(mu-3*sigma-0.4, -0.03, 'μ-3σ', fontsize = 10) # μ-3σ plt.text(mu+3*sigma-0.4, -0.03, 'μ+3σ', fontsize = 10) # μ+3σ plt.text(mu-0.5, 0.16, '95.5%', fontsize = 14) # 95.5% # 軸目盛を調整 plt.ylim(0, 0.45) # y軸目盛範囲 plt.xticks(color="None") # x軸ラベルを消去 plt.yticks(color="None") # y軸ラベルを消去 plt.show() # 所与の確率分布を仮定(3σ範囲) # 所与の期待値と分散 mu = 0 variance = 1 # 期待値と分散(標準偏差)をもとに確率密度関数を生成 data_2 = np.linspace(mu-4, mu+4, 100) sigma = np.sqrt(variance) norm_pdf = stats.norm.pdf(x = data_2, loc = mu, scale = sigma) # 確率密度を取得 norm_pdf_max = np.max(norm_pdf) # 確率密度の最大値 lower_1 = stats.norm.pdf(x=mu-sigma, loc=mu, scale=sigma) # 確率変数μ-σ upper_1 = stats.norm.pdf(x=mu+sigma, loc=mu, scale=sigma) # 確率変数μ+σ lower_2 = stats.norm.pdf(x=mu-2*sigma, loc=mu, scale=sigma) # 確率変数μ-2σ upper_2 = stats.norm.pdf(x=mu+2*sigma, loc=mu, scale=sigma) # 確率変数μ+2σ lower_3 = stats.norm.pdf(x=mu-3*sigma, loc=mu, scale=sigma) # 確率変数μ-3σ upper_3 = stats.norm.pdf(x=mu+3*sigma, loc=mu, scale=sigma) # 確率変数μ+3σ # 確率密度関数のグラフを描画 plt.plot(data_2, norm_pdf, color = "tab:cyan", lw = 4) # 垂直線 plt.vlines(x=mu, ymin=0, ymax=norm_pdf_max, color="orange", lw=1.5) # μ plt.vlines(mu-sigma, 0, lower_1, color="black", lw=1, linestyle=':') # μ-σ plt.vlines(mu+sigma, 0, upper_1, color="black", lw=1, linestyle=':') # μ+σ plt.vlines(mu-2*sigma, 0, lower_2, color="black", lw=1, linestyle=':') # μ-2σ plt.vlines(mu+2*sigma, 0, upper_2, color="black", lw=1, linestyle=':') # μ+2σ plt.vlines(mu-3*sigma, 0, lower_3, color="black", lw=0.8) # μ-3σ plt.vlines(mu+3*sigma, 0, upper_3, color="black", lw=0.8) # μ+3σ # テキストを配置 plt.text(mu-0.2, -0.03, 'μ', fontsize = 13, color = "darkorange") # μ plt.text(mu-sigma-0.4, -0.03, 'μ-σ', fontsize = 10) # μ-σ plt.text(mu+sigma-0.4, -0.03, 'μ+σ', fontsize = 10) # μ+σ plt.text(mu-2*sigma-0.4, -0.03, 'μ-2σ', fontsize = 10) # μ-2σ plt.text(mu+2*sigma-0.4, -0.03, 'μ+2σ', fontsize = 10) # μ+2σ plt.text(mu-3*sigma-0.4, -0.03, 'μ-3σ', fontsize = 12) # μ-3σ plt.text(mu+3*sigma-0.4, -0.03, 'μ+3σ', fontsize = 12) # μ+3σ plt.text(mu-0.5, 0.16, '99.7%', fontsize = 14) # 99.7% # 軸目盛を調整 plt.ylim(0, 0.45) # y軸目盛範囲 plt.xticks(color="None") # x軸ラベルを消去 plt.yticks(color="None") # y軸ラベルを消去 plt.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

朝飯前に学べる!便利なPythonのヒント100選【前編】

本記事は、Fatos Morina氏による「100 Helpful Python Tips You Can Learn Before Finishing Your Morning Coffee」(2021年5月10日公開)の和訳を、著者の許可を得て掲載しているものです。 朝飯前に学べる!便利なPythonのヒント100選【前編】 Photo by Jexo on Unsplash はじめに Pythonは、主にそのシンプルさと習得のしやすさから、最近では非常に人気があります。 データサイエンスや機械学習、ウェブ開発、スクリプト記述、自動化など、幅広い分野で利用することができます。 この記事はかなり長いので、すぐ始めましょう。 1. forループのelse条件 今までにPythonのあらゆるコードを見てきたとしても、次のfor-elseは見逃している可能性があります。私も数週間前に初めて見ました。 これはリストを繰り返すfor-elseメソッドで、リストを反復処理しているにもかかわらずelse条件もあるという、非常に珍しいものです。これは、JavaやRuby、JavaScriptなどの他のプログラミング言語では見られません。 実際にどのようなものか、例を見てみましょう。例えば、リストに奇数がないか調べているとします。リストを反復します。 numbers = [2, 4, 6, 8, 1] for number in numbers: if number % 2 == 1: print(number) break else: print("No odd numbers") 奇数が見つかった場合、breakを実行しelseブランチをスキップするため、その数字を出力します。それ以外はbreakを実行しないため、実行フローはelseブランチを実行します。 この例では、1を出力します。 2. 名前付き変数を使用して、リストから要素を取得する my_list = [1, 2, 3, 4, 5] one, two, three, four, five = my_list 3. heapqモジュールを使用して、リストの最大値または最小値からn個の要素を取得する import heapq scores = [51, 33, 64, 87, 91, 75, 15, 49, 33, 82] print(heapq.nlargest(3, scores)) # [91, 87, 82] print(heapq.nsmallest(5, scores)) # [15, 33, 33, 49, 51] 4. リストの値をメソッドの引数として渡す リストのすべての要素を抽出するには、「*」を使用します。 my_list = [1, 2, 3, 4] print(my_list) # [1, 2, 3, 4] print(*my_list) # 1 2 3 4 これは、リストのすべての要素をメソッドの引数として渡す時に役立ちます。 def sum_of_elements(*arg): total = 0 for i in arg: total += i return total result = sum_of_elements(*[1, 2, 3, 4]) print(result) # 10 5. リストの中央にあるすべての要素を取得する _, *elements_in_the_middle, _ = [1, 2, 3, 4, 5, 6, 7, 8] print(elements_in_the_middle) # [2, 3, 4, 5, 6, 7] 6. 複数の変数に1行で代入する one, two, three, four = 1, 2, 3, 4 7. リスト内包表記 内包表記を使用して、1行のコードでリストを繰り返します。例えば、リストの数値を2乗します。 numbers = [1, 2, 3, 4, 5] squared_numbers = [num * num for num in numbers] print(squared_numbers) 内包表記は、リストの操作だけではありません。辞書、集合、ジェネレータでも使用できます。 辞書内包表記を使用して、辞書の値を2乗する例を見てみましょう。 dictionary = {'a': 4, 'b': 5} squared_dictionary = {key: num * num for (key, num) in dictionary.items()} print(squared_dictionary) # {'a': 16, 'b': 25} 8. Enumを使用して、同じ概念の関連項目を列挙する ドキュメントによると Enumは、一意の値にバインドされた識別名の集合です。グローバル変数と似ていますが、より便利なrepr()やグループ化、型安全性などの機能があります。 例です。 from enum import Enum class Status(Enum): NO_STATUS = -1 NOT_STARTED = 0 IN_PROGRESS = 1 COMPLETED = 2 print(Status.IN_PROGRESS.name) # IN_PROGRESS print(Status.COMPLETED.value) # 2 9. ループなしで文字列を繰り返す name = "Banana" print(name * 4) # BananaBananaBananaBanana 10. 数学のように3つの数字を比較する ある値が他の2つの値の間にあるか比較したい場合、数学で使う簡単な式があります。 1 < x < 10 この式は、小学校で学ぶ代数式ですが、Pythonでも使えます。 そうです。今まで、このような形式の比較をしたことがあるでしょう。 1 < x and x < 10 Pythonでは、次のようにすればいいのです。 1 < x < 10 x = 3 print (1 < x < 10) # True これは、プログラマを幸せにすることを目的に開発されたプログラミング言語であるRubyでは使えません。JavaScriptでは使えることが分かりました。 こんなに簡単な式があまり話題になっていないことに、本当に驚きました。少なくとも私は、話題になっているのをほとんど見たことがありません。 11. 辞書を読み取り可能な1行にマージする これは、Python 3.9から利用可能です。 first_dictionary = {'name': 'Fatos', 'location': 'Munich'} second_dictionary = {'name': 'Fatos', 'surname': 'Morina', 'location': 'Bavaria, Munich'} result = first_dictionary | second_dictionary print(result) # {'name': 'Fatos', 'location': 'Bavaria, Munich', 'surname': 'Morina'} 12. タプルの要素のインデックスを取得する books = ('Atomic habits', 'Ego is the enemy', 'Outliers', 'Mastery') print(books.index('Mastery')) # 3 13. 文字列を文字列のリストに変換する ある関数の入力が、リストであるべきところ文字列だったとします。 input = "[1,2,3]" このような書式ではなく、リストにする必要があります。 input = [1,2,3] または、API呼び出しから次のような応答があるかもしれません。 input = [[1, 2, 3], [4, 5, 6]] 複雑な正規表現に煩わされずに、astモジュールをimportして、そのliteral_evalメソッドを呼び出すだけでよいのです。 import ast def string_to_list(string): return ast.literal_eval(string) それだけです。これで、次のようなリストまたはリストのリストとして結果を取得します。 import ast def string_to_list(string): return ast.literal_eval(string) string = "[[1, 2, 3],[4, 5, 6]]" my_list = string_to_list(string) print(my_list) # [[1, 2, 3], [4, 5, 6]] 14. 名前付きパラメータを使用して、凡ミスを防ぐ 2つの数値の差を計算するとします。差は非可換です。 a - b != b -a しかし、パラメータの順序を忘れてしまうと、凡ミスをすることがあります。 def subtract(a, b): return a - b print((subtract(1, 3))) # -2 print((subtract(3, 1))) # 2 このような潜在的なミスを避けるためには、名前付きのパラメータを使用するだけでよく、パラメータの順序はもはや問題ではなくなります。 def subtract(a, b): return a - b print((subtract(a=1, b=3))) # -2 print((subtract(b=3, a=1))) # -2 15. 複数の要素を1つのprint()文で出力する print(1, 2, 3, "a", "z", "this is here", "here is something else") 16. 複数の要素を1行で出力する print("Hello", end="") print("World") # HelloWorld print("Hello", end=" ") print("World") # Hello World print('words', 'with', 'commas', 'in', 'between', sep=', ') # words, with, commas, in, between 17. カスタム区切りを挿入した複数の値を出力する 高度な出力を簡単に行うことができます。 print("29", "01", "2022", sep="/") # 29/01/2022 print("name", "domain.com", sep="@") # name@domain.com 18. 変数名の先頭に数字を使用できない four_letters = "abcd" # this works 4_letters = "abcd" # this doesn't work 19. 変数名の先頭に演算子を使用できない +variable = "abcd" # this doesn't work 20. 数値の最初の桁に0を使用できない number = 0110 # this doesn't work 21. 変数名のどこにでもアンダースコアを使用できる 変数名のどこにでも、何回でも、使えるということです。 a______b = "abcd" # this works _a_b_c_d = "abcd" # this also works 使うことを推奨している訳ではありませんが、このような変な変数名を見かけた場合は、実際に有効な変数名であることを知っておいてください。 22. アンダースコアを使用して、大きな数字を区切る これにより、より読みやすくなります。 print(1_000_000_000) # 1000000000 print(1_234_567) # 1234567 23. リストの順序を反転する my_list = ['a', 'b', 'c', 'd'] my_list.reverse() print(my_list) # ['d', 'c', 'b', 'a'] 24. ステップ関数を使用して、文字列をスライスする my_string = "This is just a sentence" print(my_string[0:5]) # This # Take three steps forward print(my_string[0:10:3]) # Tsse 25. スライスを反転する my_string = "This is just a sentence" print(my_string[10:0:-1]) # suj si sih # Take two steps forward print(my_string[10:0:-2]) # sjs i 26. 開始または終了インデックスだけを使用して、部分スライスする スライスの開始と終了を示すインデックスはオプションです。 my_string = "This is just a sentence" print(my_string[4:]) # is just a sentence print(my_string[:3]) # Thi 27. フロア分割 print(3/2) # 1.5 print(3//2) # 1 28. 「==」と「is」の違い 「is」は、2つの変数がメモリ内の同じオブジェクトを指しているか調べます。 「==」は、2つのオブジェクトが保持する値の同一性を比較します。 first_list = [1, 2, 3] second_list = [1, 2, 3] # Is their actual value the same? print(first_list == second_list) # True # Are they pointing to the same object in memory print(first_list is second_list) # False, since they have same values, but in different objects in memory third_list = first_list print(third_list is first_list) # True, since both point to the same object in memory 29. 2つの辞書を簡単にマージする dictionary_one = {"a": 1, "b": 2} dictionary_two = {"c": 3, "d": 4} merged = {**dictionary_one, **dictionary_two} print(merged) # {'a': 1, 'b': 2, 'c': 3, 'd': 4} 30. 文字列が別の文字列よりも大きいか調べる first = "abc" second = "def" print(first < second) # True second = "ab" print(first < second) # False 31. インデックス0を使用せずに、文字列が特定の文字で始まるか調べる my_string = "abcdef" print(my_string.startswith("b")) # False 32. id()を使用して、変数の一意のIDを取得する print(id(1)) # 4325776624 print(id(2)) # 4325776656 print(id("string")) # 4327978288 33. 整数、浮動小数点数、文字列、ブール、タプルは不変である 整数、浮動小数点数、文字列、ブール、タプルなどの不変型に変数を代入すると、その変数はメモリ内のオブジェクトを指します。 その変数に別の値を代入した場合、元のオブジェクトはまだメモリ内に残っていますが、それを指す変数は失われます。 number = 1 print(id(number)) # 4325215472 print(id(1)) # 4325215472 number = 3 print(id(number)) # 4325215536 print(id(1)) # 4325215472 34. 文字列とタプルは不変である 既出ですが、非常に重要なので強調したいと思います。 name = "Fatos" print(id(name)) # 4422282544 name = "fatos" print(id(name)) # 4422346608 my_tuple = (1, 2, 3, 4) print(id(my_tuple)) # 4499290128 my_tuple = ('a', 'b') print(id(my_tuple)) # 4498867584 35. リスト、集合、辞書は可変である オブジェクトへのバインドを失わずに、オブジェクトを変更できます。 cities = ["Munich", "Zurich", "London"] print(id(cities)) # 4482699712 cities.append("Berlin") print(id(cities)) # 4482699712 集合を使用する例です。 my_set = {1, 2, 3, 4} print(id(my_set)) # 4352726176 my_set.add(5) print(id(my_set)) # 4352726176 36. 集合を不変集合に変換する これにより、変更できなくなります。 my_set = frozenset(['a', 'b', 'c', 'd']) my_set.add("a") これにより、エラーが発生します。 AttributeError: 'frozenset' object has no attribute 'add' 37. if-elifブロックは、最後にelseブロックがなくてもよい ただし、elifはその前にifステップが必要です。 def check_number(number): if number > 0: return "Positive" elif number == 0: return "Zero" return "Negative" print(check_number(1)) # Positive 38. sorted()を使用して、2つの文字列がアナグラムか調べる def check_if_anagram(first_word, second_word): first_word = first_word.lower() second_word = second_word.lower() return sorted(first_word) == sorted(second_word) print(check_if_anagram("testinG", "Testing")) # True print(check_if_anagram("Here", "Rehe")) # True print(check_if_anagram("Know", "Now")) # False 39. Unicodeで文字の値を取得する print(ord("A")) # 65 print(ord("B")) # 66 print(ord("C")) # 66 print(ord("a")) # 97 40. 辞書のキーを1行で取得する dictionary = {"a": 1, "b": 2, "c": 3} keys = dictionary.keys() print(list(keys)) # ['a', 'b', 'c'] 41. 辞書の値を1行で取得する dictionary = {"a": 1, "b": 2, "c": 3} values = dictionary.values() print(list(values)) # [1, 2, 3] 42. 辞書のキーと値を入れ替える dictionary = {"a": 1, "b": 2, "c": 3} reversed_dictionary = {j: i for i, j in dictionary.items()} print(reversed) # {1: 'a', 2: 'b', 3: 'c'} 43. ブール値を数値に変換する print(int(False)) # 0 print(float(True)) # 1.0 44. 算術演算でブール値を使用する Falseは0、Trueは1です。 x = 10 y = 12 result = (x - False)/(y * True) print(result) # 0.8333333333333334 45. データ型をブール値に変換する print(bool(.0)) # False print(bool(3)) # True print(bool("-")) # True print(bool("string")) # True print(bool(" ")) # True 46. 値を複素数に変換する print(complex(10, 2)) # (10+2j) print(hex(11)) # 0xb 47. リストの先頭に値を追加する append()を使用して、右から新しい値を挿入します。 insert()を使用して、新しい要素を挿入するインデックスと要素を指定できます。この場合は、先頭に挿入するため、インデックスは0を使用します。 my_list = [3, 4, 5] my_list.append(6) my_list.insert(0, 2) print(my_list) # [2, 3, 4, 5, 6] 48. lambda関数は1行でしか記述できない lambda関数は複数行で記述できません。 次のようにしてみましょう。 comparison = lambda x: if x > 3: print("x > 3") else: print("x is not greater than 3") 次のエラーが発生します。 result = lambda x: if x > 3: ^ SyntaxError: invalid syntax 49. lambdaの条件文にはelse部が必須である 次のようにしてみましょう。 comparison = lambda x: "x > 3" if x > 3 次のエラーが発生します。 comparison = lambda x: "x > 3" if x > 3 ^ SyntaxError: invalid syntax なお、これは条件式の機能であり、lambda 自体の機能ではありません。 この記事がお役に立てれば幸いです。ハッピーコーディング! 「フロントエンド開発者のための刺激的なプロジェクト10選【後編】」もご覧ください。 翻訳協力 この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。 Original Author: Fatos Morina (https://www.linkedin.com/in/fatosimorina/) Original Article: 100 Helpful Python Tips You Can Learn Before Finishing Your Morning Coffee Thank you for letting us share your knowledge! 選定担当: @gracen 翻訳担当: @gracen 監査担当: - 公開担当: @gracen ご意見・ご感想をお待ちしております 今回の記事はいかがでしたか? ・こういう記事が読みたい ・こういうところが良かった ・こうした方が良いのではないか などなど、率直なご意見を募集しております。 頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。 皆様のメッセージをお待ちしております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【機械学習】脳死で標準化したい!(StandardScaler)

サクッと標準化したいけど、ごちゃごちゃした解説はいらないよ〜という人向けに、お手軽で実装できる標準化の方法を書いてみました。 1. StandardScalerとは 機械学習によく使われるライブラリであるscikit-learnの中に入っている標準化のためのクラスです。 JupyterNotebookやGoogleCollaboratoryを使う場合にはscikit-learnをimportするだけで使えますが、ローカルな環境で機械学習を動かすときには事前にインストールしておきましょう。 $ pip install scikit-learn 2. 標準化とは?【1分でわかるように解説】 機械学習モデルに食べさせるデータは、そのままだと大きさがバラバラすぎて上手に食べてくれません。そこで、標準化を行って機械学習が食べやすいサイズに加工してあげる必要があります。 たとえば、身長と体重のデータがあるとします。身長は150~185。体重は40~100くらいに収まるとします。 そのとき、機械はより見た目の数字が大きい身長の方が大事なデータだと勘違いして、体重を軽視して学習してしまいます。アホの子っぽくてかわいいですね。 なので、標準化を行って身長も体重も同じくらいの大きさに加工してあげる必要があります。 具体的には、標準偏差を1にしてデータのサイズ感を統一します。 3. お手軽な標準化のやり方 ◎下準備編 これを読んでいる皆さんは、分析したいデータを放り込んでください。 私はscikit-learnにもともと含まれているデータセットiris(アヤメの花の品種)を読み込んで、pandasでデータフレームに変換します。 データセットの中にカテゴリ変数が含まれているとエラーを吐いてくるので、以下のサイトなどを参照してあらかじめダミー変数にしておきましょう。(例:[Yes, No, Yes]→[1, 0, 1]) 【参考:『機械学習でのカテゴリデータの扱い方』 https://qiita.com/QUANON/items/08a65012366abd150172 】 import sklearn import pandas as pd from sklearn.datasets import load_iris df = pd.DataFrame(iris.data, columns=iris.feature_names) df.describe() ◎実践編 いよいよ標準化です。 まず、scikit-learn(sklearn)のpreprocessingモジュールからStandardScalerをインポートします。 次に、std_scalerにStandardScalerインスタンスを生成。 std_scaler.fitでインスタンスがデータフレームを扱えるように適応してくれます。 最後にstd_scaler.transformで標準化して、pd.DataFrameでデータフレーム型にしてしまえば完成です!簡単! from sklearn.preprocessing import StandardScaler std_scaler = StandardScaler() std_scaler.fit(df) df_std = pd.DataFrame(std_scaler.transform(df), columns=df.columns) df_std.describe() stdの行がすべて同じ数字になっているのを確認できたら標準化は完璧!! 説明がよくわからなかったら「df」と書いてある部分を全部ご自分のデータフレームが入った変数名に書き換えて使ってみましょう。 ね? 脳死で出来たでしょう? 参考 この記事は以下のサイトを参考にして書きました。 非常に参考になる記事なので、ぜひこちらも見ていってください。 【1】…………『Scikit-learnで標準化をする時にStandardScalerを使ってみた話』, EnsekiTTTさん, https://ensekitt.hatenablog.com/entry/2018/08/08/200000 【2】…………『How and why to Standardize your data: A python tutorial』, Serafeim Loukas, https://towardsdatascience.com/how-and-why-to-standardize-your-data-996926c2c832
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

住宅ローンの繰上げ返済はすべき?

はじめに 以前、住宅ローンの計算を自分でしてみたい という備忘録を投稿しました。 その後、「繰上げ返済」って計算上どういう結果、つまり、トータルの支払額がいくらになるとか、トータルの利息がいくらになるとかも計算してみようかなと思い計算してみたので備忘録として再度投稿します。 繰上げ返済 「繰上げ返済」とは、ほとんど説明する必要もなく、ローンを返済中にお金を一部先払いする事で、 返済額軽減型 => 毎月の支払額を低減 返済期間短縮型 => 支払い期間を短縮する の2種類があります。 英語だと advanced/advance payment paying/repaying loan/debt in advance とかいうみたいですね。 prepayment も「繰上げ返済」みたいな意味で辞書には出てくるみたいだけど、プリペイドSIMの先払いとかと区別付かなくなってどうなんだろうって感じですね。 設定 こちらに関しても、以前の投稿 「住宅ローンの計算を自分でしてみたい」 と同じ設定にします。 現時点での残債 : 2000万円 残りの返済期間 : 20年(240ヶ月) 定年までの年数 : 15年 金利 : 0.45%(変動金利) 返済額軽減型の計算 この条件の時の支払い金額は、87,156円でした。 この時、10万円、20万円 ・・・ 100万円と繰上げ返済した場合に、毎月の支払い金額がいくらになるか計算してみました。 import numpy as np import math # 年利 rate = 0.0045 # 支払い月 nper = 20 * 12 # 残金 pv = -(2000 * 10000) # 毎月の支払額 pmt = np.pmt(rate/12, nper, pv, fv=0, when='end') print('現時点での毎月の支払額 : {}'.format(math.ceil(pmt))) # 繰上げ返済をした場合の支払額 for partial_payment in np.arange(10, 101,10): reduced_payment = pv + (partial_payment * 10000) pmt = np.pmt(rate/12, nper, reduced_payment, fv=0, when='end') print('{: 4}万円繰上 =>{}'.format(partial_payment, math.ceil(pmt))) 現時点での毎月の支払額 : 87156 10万円繰上 => 86720 20万円繰上 => 86284 30万円繰上 => 85848 40万円繰上 => 85413 50万円繰上 => 84977 60万円繰上 => 84541 70万円繰上 => 84105 80万円繰上 => 83669 90万円繰上 => 83234 100万円繰上 => 82798 50万円繰り上げると、2,179円、100万円だと 4,358円 も支払額が下がり、それなりにアレだなぁと感じられる金額では無いでしょうか? こうなると、利息の総額も下がるはず。 import numpy as np import math def calc_total_payment(race, nper, pv): total_interest = 0 pmt = np.pmt(rate/12, nper, pv, fv=0, when='end') principal= np.ppmt(rate/12, range(nper), nper,pv) for i, p in enumerate(principal): pmt = np.pmt(rate/12, nper, pv, fv=0, when='end') principal= np.ppmt(rate/12, range(nper), nper,pv) # 当月の金利を計算 interest_the_month = math.ceil(pmt-p) # 支払った金利を加算 total_interest += interest_the_month return total_interest # 年利 rate = 0.0045 # 支払い月 nper = 20 * 12 # 残金 pv = -(2000 * 10000) print('総支払い利息 => {}'.format(calc_total_payment(rate, nper, pv))) # 繰上げ返済をした場合の支払利息 for partial_payment in np.arange(10, 101,10): reduced_payment = pv + (partial_payment * 10000) print('{: 4}万円繰上 => {}'.format(partial_payment, calc_total_payment(rate, nper, reduced_payment))) 総支払い利息 => 924861 10万円繰上 => 920238 20万円繰上 => 915618 30万円繰上 => 910993 40万円繰上 => 906368 50万円繰上 => 901746 60万円繰上 => 897125 70万円繰上 => 892492 80万円繰上 => 887862 90万円繰上 => 883248 100万円繰上 => 878633 50万円繰り上げ返済すれば、総支払い利息が 23,115円減少。 100万円だと 46,228円減少。 そうか、そうか。 返済期間短縮型の計算 返済期間を短縮する場合は、例えば、10万円を返済すると、毎月の支払額 87,156円より多い金額を返済するので、支払い月を一つ減らせばいいのかな? import numpy as np import math # 年利 rate = 0.0045 # 支払い月 nper = 20 * 12 - 1 # 残金 pv = -(2000 * 10000) + 100000 # 毎月の支払額 pmt = np.pmt(rate/12, nper, pv, fv=0, when='end') print('現時点での毎月の支払額 : {}'.format(math.ceil(pmt))) 現時点での毎月の支払額 : 87067 支払い金額自体が変わってしまうので、この計算方法は正しく無いんだと思う。 とりあえず、参考の情報として 同じ考え方で計算してみる事にします。 import numpy as np import math def calc_total_payment(race, nper, pv): total_interest = 0 pmt = np.pmt(rate/12, nper, pv, fv=0, when='end') principal= np.ppmt(rate/12, range(nper), nper,pv) for i, p in enumerate(principal): pmt = np.pmt(rate/12, nper, pv, fv=0, when='end') principal= np.ppmt(rate/12, range(nper), nper,pv) # 当月の金利を計算 interest_the_month = math.ceil(pmt-p) # 支払った金利を加算 total_interest += interest_the_month return total_interest # 年利 rate = 0.0045 # 支払い月 nper = 20 * 12 # 残金 pv = -(2000 * 10000) montly_payment = 87156 print('総支払い利息 => {}'.format(calc_total_payment(rate, nper, pv))) # 繰上げ返済をした場合の支払利息 for partial_payment in np.arange(10, 101,10): reduced_payment = pv + (partial_payment * 10000) shorten_period = int((partial_payment * 10000) / montly_payment) print('{: 4}万円繰上 => {}'.format(partial_payment, calc_total_payment(rate, nper - shorten_period, reduced_payment))) 総支払い利息 => 924861 10万円繰上 => 916396 20万円繰上 => 907967 30万円繰上 => 899583 40万円繰上 => 891226 50万円繰上 => 882911 60万円繰上 => 874645 70万円繰上 => 862691 80万円繰上 => 854514 90万円繰上 => 846378 100万円繰上 => 838276 50万円繰り上げ返済すれば、総支払い利息が 41,950円減少。 100万円だと 86,585円減少。 元々の総支払利息 : 924,861 繰上額 返済軽減 期間短縮 10万円 920,238 916,396 20万円 915,618 907,967 30万円 910,993 899,583 40万円 906,368 891,226 50万円 901,746 882,911 60万円 897,125 874,645 70万円 892,492 862,691 80万円 887,862 854,514 90万円 883,248 846,378 100万円 878,633 838,276 金額的には、期間短縮型の方が倍ぐらい総支払い利息が少ないという事になるみたいです。 前述したように、この計算方法だと毎月の支払い金額自体が減ってしまうので若干実態とは異なるようですが、あくまで参考として、総支払い利息は「返済額軽減型」より「期間短縮型」が倍ぐらい少ないという事と理解しました。 元々の目的なんだっけ? とりあえず、繰り上げ返済するとどれぐらい総支払い利息が減るのかを計算してみましたが、そもそも「住宅ローンの繰上げ返済はすべき?」がテーマでした。 繰り上げ返済すれば、総支払い利息が減るのは計算するまでも無い事で、どちら(返済軽減 or 期間短縮) の 総支払い利息 が有利かも計算しなくても、 住宅ローンの繰り上げ返済「期間短縮型」と「返済額軽減型」、有利なのはどっち? このへんを読めばわかる話。 で、するかしないかで考えれば、繰り上げ返済でへる利息よりも資産運用等で増やせる額が多いかどうかを認識すれば腹落ちする答えになりそう。 もし、年利 2% で運用できる投資信託があった場合に、その投資信託に 100万円預けた時の一年後はいくらになっているだろうか? ※ 日本では、再投資する場合も所得税・住民税を合わせて20.315%の税金が課されますので、実際の利益は2割引でお考えください import numpy as np np.fv(0.02, 1, 0, -100) 102.0 102万円。そりゃそうでしょうね。 ただ、この2万円の利益を再投資に回して20年経つといくらになるのか? import numpy as np np.fv(0.02, 20, 0, -100) 148.59473959783548 148万円!48万円の利益! 税金を考慮して2割引ぐらいに考えても、35 ~ 38 万ぐらい増えると考えれば、投資信託に放置して、分配金を再投資した方が特や!ってなりますね。 ただ、これは投資運用がうまく行った場合の話。投資先やこれからの社会の情勢によっては、繰上げ返済の利息軽減分以上の利益どころか、原価割れだってあり得る。 結局はその人の価値観 結論としては、何を求めるかによるって事になると思います。 1) 純粋にトータルでよりお金を残したい場合 リスクを受け入れる覚悟があり、総支払い利息の削減分よりも運用益を計算上求められる自信・根拠があるのであれば運用に回すべき。 2) 何よりもリスクを下げたい この場合は、期間短縮型の繰上げ返済がとるべき手段として最有力。 3) 毎月のキャッシュフローがきつい この場合は、返済軽減の繰上げ返済になるんでしょうね。 また、毎月の支払いが減った分を貯蓄して、再度、繰り上げ返済に回すという戦略もあるのかも。 まあ、こんな事は最初からわかっている事ですが、実際に自分で計算して、色々シミュレーションしてみて出てきた数字を見ながら結論を出せば、自分として腹落ちできる対応をしていると言えるんじゃ無いでしょうか? 自分が納得しての行動であれば、少々悪い結果になっても、それほど後悔しないでしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python で開平法の手順を使った平方根の近似値を求める処理を作成してみた

バージョン Python 3.8.3 開平法 筆算で正の数の平方根を任意の桁まで求められる方法 今回はあえてプログラムで同じ手順で平方根を求めてみました 参考 開平法の手順|思考力を鍛える数学 math.sqrt で一発で平方根は出せるのでこんな処理は必要はない from math import sqrt print(sqrt(3)) # 1.7320508075688772 print(sqrt(100)) # 10.0 print(sqrt(111)) # 10.535653752852738 print(sqrt(277729)) # 527.0 print(sqrt(23480.352289)) # 153.233 このように簡単に平方根の値は出せるので今回のようにわざわざ作る必要はありません。 私が python と数学の勉強のために作ってみた処理になります。 何か間違いなどありましたら教えていただけるとありがたいです。 作ってみた開平法の処理 from sympy import Symbol, solve from sys import argv from math import sqrt, floor, ceil _, num, digit = argv result = '' cnt = added_num = minused_num = 0 x = Symbol('x') # 整数部分と小数点数部分で2回ループする for i in range(2): if int(digit) <= cnt: break split_num = num.split('.')[i] if i == 0 or '.' in num else '' if i == 0: # 1週目は整数部分なので最初を0埋め formatted_num = ('0' if len(split_num) % 2 else '') + split_num else: # 2週目は小数点数部分なので最後を0埋め formatted_num = split_num + ('0' if len(split_num) % 2 else '') result += '.' two_num_list = [int(v + formatted_num[i + 1]) for i, v in enumerate(formatted_num) if i % 2 == 0] # 2桁ごとに分割した数字単位でループ j = 0 while int(digit) > cnt: if i == 0 and len(two_num_list) <= j: # 整数部分を計算したので小数点部分へ行く break target_num = int(str(minused_num) + (str(two_num_list[j]) if len(two_num_list) > j else '00')) if cnt == 0: # 最初の1回だけべき根以下の最大の自然数を取得 calc_stacking_num = result_num = floor(sqrt(target_num)) else: # 2週目以降 # x*(added_num*10+x) が target_num 以下の最大の自然数を取得 result_num = [floor(v) for v in solve(x * (added_num * 10 + x) - target_num, x) if v >= 0][0] calc_stacking_num = int(str(added_num) + str(result_num)) result += str(result_num) # calc_stacking_num と下一桁を足す last_num = int(str(calc_stacking_num)[-1]) added_num = calc_stacking_num + last_num # 次の two_digit と合わせるために引いた数を取得しておく minused_num = target_num - result_num * calc_stacking_num cnt += 1 j += 1 print(result) $ # python kaiheihou.py [平方根を求めたい数字] [処理を繰り返す桁数] $ python kaiheihou.py 3 10 1.732050807 $ python kaiheihou.py 100 5 10.000 $ python kaiheihou.py 111 20 10.535653752852738848 $ python kaiheihou.py 277729 5 527.00 $ python kaiheihou.py 23480.352289 10 # 小数点数もいける! 153.2330000
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DataFrameでSettingWithCopyWarningの意味と対処法

Pandasの DataFrame でSettingWithCopyWarningの警告の意味と対処方法について書きます。 DataFrame使っているとSettingWithCopyWarningによく遭遇していました。その度にその場しのぎの修正をして対応していましたが、さすがにそろそろ根本的に理解しないと時間がもったいないと思い、この記事で整理しました。 公式Pandasのヘルプと偉大な先人が書いてくれた以下の記事が非常にわかりやすいです。この記事では、簡潔に内容を書きます。 結論 結論から先に述べます。 暗黙のCopyをやめる。そのためにChain Indexingをやめる。警告をなくすにはそれだけです。 現象 SettingWithCopyWarningが起こる一例を最初に紹介します。 シンプルなDataFrameを作ります。 import pandas as pd df = pd.DataFrame([[1, 11], [2, 11], [3, 33]], columns=['A', 'B'], index=['a', 'b', 'c']) print(df) DataFrameの最初の中身 A B a 1 11 b 2 11 c 3 33 で、B列の値が11のA列の値を代入しようとします。 df[df['B']==11]['A'] = 4 print(df) 値は変わりません。 DataFrameの最初の中身 A B a 1 11 b 2 11 c 3 33 で、SettingWithCopyWarningが出力。 <ipython-input-62-77f8496ed860>:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df[df['B']==11]['A'] = 4 警告が起こる前提知識(View or Copy) SettingWithCopyWarningを理解するためには、DataFrame処理の種類を知る必要があります。 PandasはDataFrameを処理するときにViewまたはCopyを返します。 ViewとCopyで以下のように動きが異なります。 上図の右側のCopyに対して値代入をした場合にSettingWithCopyWarningが発生し、元のDataFrameへの値代入が行われません。理由は元のDataFrameと別メモリのオブジェクトだからです。 つまり、値代入時にCopyではなくViewを使うことで警告がなくなり、問題が発生しません。 警告発生パターンと対処法 PandasはCopyを返すかViewを返すかの関数が明示的に決まっているわけでないので、問題を単純化できません。そのため、多くの人が悩みます。詳しくは記事「pandasのSettingWithCopyWarningを理解する (3/3)」の「歴史」章を読んでください。 Chained Indexing Chained Indexingの再現条件 最も基本的なSettingWithCopyWarningが発生するパターンはChained Indexingと呼ばれる処理です。 下記のようにdf[df['B']==11]という抽出処理に、さらに['A']の抽出を重ねて(Chainして)います。この場合はViewではなくCopyとなるみたいです。また、警告が発生する以外にもそもそもパフォーマンスが悪く望ましくない書き方です。 ブールインデックスを使っていますが、queryメソッドでも同じです。 ※記事「DataFrameレシピ: データ抽出条件」にブールインデックスやquery関数を使った抽出方法も書いています。 df[df['B']==11]['A'] = 4 print(df) 実行しても、Copyなので元のDataFrameであるdfの値は変化がありません。 実行結果 A B a 1 11 b 2 11 c 3 33 で、警告が出力されます。 警告内容 <ipython-input-62-77f8496ed860>:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df[df['B']==11]['A'] = 4 Chained Indexing対処法 警告で出ているTry using .loc[row_indexer,col_indexer] = value insteadをそのまま実施。 locプロパティを使います。 df.loc[df['B']==11, ['A']] = 4 print(df) 警告は消え、A列の値が置き換わっているのがわかります。 実行結果 A B a 4 11 b 4 11 c 3 33 隠れ連鎖 一見するとChained Indexingを使っていないように見える隠れた連鎖(Chain)です。 隠れ連鎖の再現条件 今度は、copyしたDataFrameに対して代入をした場合です。df2というdfのCopyを作り、列Aに値の代入をします。df2['A'] = 4部分だけを見るとなぜ警告が出るのか理解できないパターンです。 df2 = df[df['B']==11] df2['A'] = 4 print('--df--') print(df) print('--df2--') print(df2) dfの値は変わっておらずCopyであるdf2は値が変更されます。 実行結果 --df-- A B a 1 11 b 2 11 c 3 33 --df2-- A B a 4 11 b 4 11 警告が出力されます。 警告内容 <ipython-input-82-a08d45cf3537>:2: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['A'] = 4 上記の警告を見て単純にloc使っても警告は出たままです。 df2 = df[df['B']==11] df2.loc[:,'A'] = 4 # df2['A'] = 4 から変更 print('--df--') print(df) print('--df2--') print(df2) 実行結果(さっきと変わらず) --df-- A B a 1 11 b 2 11 c 3 33 --df2-- A B a 4 11 b 4 11 警告内容は少し変わっています。 警告内容 /home/i348221/Apps/python/venv/py39/lib/python3.9/site-packages/pandas/core/indexing.py:1843: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy self.obj[item_labels[indexer[info_axis]]] = value 隠れ連鎖の対処法 copy関数を使ってdf2はdfのCopyだということ明示的に処理すれば警告は消えます。(熟考していませんが)おそらく、元のDataFrameであるdfの値も変えたいことは無いのではないでしょうか。 df2 = df[df['B']==11].copy() df2['A'] = 4 print('--df--') print(df) print('--df2--') print(df2) 実行結果は、警告が消える以外は変わりません。 実行結果 --df-- A B a 1 11 b 2 11 c 3 33 --df2-- A B a 4 11 b 4 11 おまけ(偽陰性) 警告が出ないけど、値代入が失敗するパターンもあります。やはりChain Indexingが原因です。警告出ないので普段からChain Indexをしない癖をつけておくことが大事みたいです。 ※偽陰性(False Negative)の意味は別記事「【入門者向け】機械学習の分類問題評価指標解説(正解率・適合率・再現率など)」を参照ください。 df.loc[df.B == 11, ('A', 'B')]['A'] = 4 print(df) 実行結果 A B a 1 11 b 2 11 c 3 33 Chain Index やめればもちろん代入成功です。 df.loc[df.B == 11, 'A'] = 4 print(df) 実行結果 A B a 4 11 b 4 11 c 3 33 他にも偽陽性(警告出る必要ないのに誤って警告出力)パターンもあるらしいですが、最新のPandasで出るかわからないので、確認していません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python演算処理】冪算(Exponentiation/Logarithmicion)と乗除算(Multiplying and Dividing)の関係について。

まずは基本中の基本たる関数概念(Funvtion Concept)そのものの定義について振り返っておきましょう。 関数 (数学) - Wikipedia 二つの数の集合X,Yがあって、Xの要素xに対してYの要素yがただ一つ定まる時、この対応を関数(Function)と呼びy=f(x)などと書き表す(fはfunctionの略だが、複数の関数への言及が不可避となる状況ではそれをy=g(x),y=h(x)などと呼び分ける)。 この時xを独立変数(Independent Variable)、yを従属変数(Dependent Variable)と呼ぶ。 また集合Xを関数fの定義域(Domain)、集合Yの部分集合$(y|y=f(x),x \in X)$をこの関数の値域(Range)という。 関数y=fxの変化の様子はxの値を横座標、yの値を縦座標としxを定義域内で変化させた時の点P(x,y)の軌跡を描くと一目瞭然となる。これが関数y=f(x)のグラフである。 【Pyrhon演算処理】同心集合③環概念と指数/対数写像概念の導入 ①群論でいう加法実数群(Additive Real Group)だと、主に加法単位元(Additive Identity)0を中心に+∞から-∞の範囲かけての均等尺(Even Scale)が定義域や値域に指定される。 【Pyrhon演算処理】同心集合②加法的同心集合とは? ②同じくその指数写像(Exponential Map)たる乗法実数群(Multiplicative Real Group)だと、主に乗法単位元(Multiplicative Identity)1を中心に0から∞の範囲にかけての対数尺(Logarithmic Scale)が定義域や値域に指定される。 【Pyrhon演算処理】同心集合①乗法的同心集合とは? 群同型 Wikipedia 加法実数群$(\mathbb{R},+)$は、すべての正の実数が乗法についてなす群$(\mathbb {R}^+,×)$に、同型写像$f(x)=e^x(x \in \mathbb{R})$によって同型である: (\mathbb {R},+) \cong (\mathbb{R}^{+},×) この定義により加法実数群と乗法実数群は群同型(Group Isomorphism)と見做されるので理論上実数環(Real Ring)として統合的に扱う事も可能とされる。【Python演算処理】環論に立脚した全体像再構築①空環と実数環 ③極座標系(Polar Coordinate System)$(r,φ),(r,φ,θ),e^{iθ}=cos(θ)+sin(θ)i$などでは、主に2πラジアンを1周期とする半径rの角度空間(Angle Space)が定義域や値域に指定される。 群同型 Wikipedia 加法整数群$(\mathbb {Z},+)$は加法実数群$(\mathbb{R},+)$の部分群であり、商群$\frac{\mathbb{R}}{\mathbb{Z}}$は、同型写像$f(x+\mathbb {Z})=e^{2πxi}(x \in \mathbb{R})$(x=0→2π)によって絶対値1の乗法複素数群$S^1$に同型である: \frac{(\mathbb{R},+)}{(\mathbb{Z},+)} \cong S^1 この定義により角度空間概念も実数環の有する性質の一つとして統合的に扱える様になるが、関数の対象としてはそれがしばしば見せる多価性(Multivalued)が問題となる。複素解析論(Complex Analysis Theory)において方便として「分岐截断(Branch Point Cut)」の概念が導入されたのはこの問題に対応する為であった(その経緯上、リーマン球面概念同様、群論的には乗法群にのみに関わってくるとも考えられそうである)。分岐点 (数学) - Wikipedia 群論では、ここでいう絶対値1の乗法複素数群=1次元球面(半径1の単位円)にリー群$S^1$(1次元トーラス)、円周群(Circle Group)$\mathbb{T}$、特殊直交群(Special Orthogonal Group)SO(2)、複素1次ユニタリ群(Unitary Group)U(1)といった様々な名前を与え、それぞれの発展形を展開する。リー群 - Wikipedia円周群 - Wikipedia直交群 - Wikipediaユニタリ群 - Wikipedia \mathbb{T}=SO(2)=U(1)=S^1=(z \in \mathbb{T}:|z|=1) また実数環を垂直軸z(高さh)に加法実数群、水平軸xy(半径rの極座標系)に乗法実数群を配した円筒座標系(Cylindrical Coordinate System)としてイメージする場合、そこに配された球表面の指数写像の取り方に「(地図投影法におけるメルカトル図法に対応する)側面射影法」と「(地図投影法における正距方位図法に対応する)底面射影法」の2種類が存在する事に留意しなければならない。前者の写像は「高さπ、幅2πの矩形」、後者の写像は「半径πの円盤」となり、関数ではこれが定義域や値域に指定される。【Python画像処理】メルカトル図法と正距方位図法 ④統計学(Statistics)の世界では「面積の合計が1となる矩形」を想定し、そのx軸に平均値0を中心に+∞から-∞の範囲で均等尺を振った説明変数(Explanatory Variable)、y軸に指定した範囲の面積計算がそのままその区画の分布確率=p値となる目的変数(Response Variable)を配したグラフが良く使われ、それがそのままそれらを扱う関数の定義域や値域に指定される事が多い。 【Pyrhon演算処理】確率密度空間と累積分布空間①記述統計との狭間 なるほど、全体像を俯瞰するとこんな感じに繋がってたんですね。 冪算(Exponentiation/Logarithmicion)と乗除算(Multiplying and Dividing)の関係 冪算(Exponentiation/Logarithmicion)$y=x^n$および$y=x^{\frac{1}{n}}$と乗算(Multiplying)$y=nx$および除算(Dividing)$\frac{x}{n}$の関係は以下となっています。 勾配概念(Gradient Concept)の導入 傾き (数学) - Wikipedia 傾きmは傾斜角θによりm=tan(θ)と定義される。また平面上の直線の傾きは、垂直移動距離Δy($y_2-y_1$)を水平移動距離Δx($x_2-x_1$)で割ったm=Δy/Δxで定義される。 これらの等式が示す様に鉛直線(y軸に平行な直線)の傾きは、零除算となり、定義されない。 $y=\lim_{n \to ∞}\frac{±x}{n^n}=0$の場合…一次関数(Linear Function)y=±axの勾配(Gradient)aが1から0にかけて推移するイメージ。 ①n=1の時、奇関数y=±xとなる。 ②これがnの増大につれ偶関数y=0に限りなく近付いていく。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #放物線x x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 yp= x/Time_Code[n]**Time_Code[n] ym= -x/Time_Code[n]**Time_Code[n] #描画 plt.plot(x,yp,color="red",marker="o",lw=1,label="(-x/n^n") plt.plot(x,ym,color="blue",marker="o",lw=1,label="(+x/n^n") plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=±x/n^n") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(0) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin001.gif", writer="pillow") $y=\lim_{n \to ∞}±x*n^n=∞$の場合…一次関数(Linear Function)y=±axの勾配(Gradient)aが1から∞にかけて推移するイメージ。 ①n=1の時はやはり奇関数y=±xとなる。 ②これがnの増大につれ偶関数x=0に限りなく近付いていくイメージ。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #放物線x x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 yp= x*Time_Code[n]**Time_Code[n] ym= -x*Time_Code[n]**Time_Code[n] #描画 plt.plot(x,yp,color="red",marker="o",lw=1,label="(-x*n^n") plt.plot(x,ym,color="blue",marker="o",lw=1,label="(+x*n^n") plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=±x*n^n") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(0) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin501.gif", writer="pillow") ここには2種類の関数(Function)の射映(Projection)が重なって現れてきます。 乗除算(Multiplying and Dividing)の射映 デカルト座標系(Cartesian Coordinate System)(x,y)上に描かれる同心円は、上掲の勾配概念(Gradient Concept)の制約故にそれぞれ対角線上に位置する(第一象限,第三象限)上と(第二象限,第四象限)上の展開が切り離されている。 関数y=f(x) 任意の定義域xに対する値域y 0 y=±x/±∞ 0 1 y=±x 座標(x,y)=(1,1)(-1,-1),(-1,1)(1,-1), 2 y=x*±∞ 座標(x,y)=(∞,∞)(-∞,-∞),(-∞,∞)(∞,-∞) import numpy as np import sympy as sp import pandas as pd X1 = np.matrix([ ["y=±x/±∞","y=±x","y=x*±∞"], ["0","座標(x,y)=(1,1)(-1,-1),(-1,1)(1,-1),","座標(x,y)=(∞,∞)(-∞,-∞),(-∞,∞)(∞,-∞)"]]) x=X1.transpose() df=pd.DataFrame(x,columns=['関数y=f(x)', '任意の定義域xに対する値域y']) sp.init_printing() org=df.to_html() print(org.replace('\n', '')) この辺りの四象限との関係は、おそらく線形代数(Linear Algebra)における行列演算(Matrix Aperation)の概念と関係してくるのです。 【Python演算処理】行列演算の基本④大源流における記述統計学との密接な関連性? \left[\begin{matrix}a & b\end{matrix}\right]\left[\begin{matrix}1 & 1\\1 & 1\end{matrix}\right]\left[\begin{matrix}a\\b\end{matrix}\right]=\left[\begin{matrix}\left(a + b\right)^{2}\end{matrix}\right]\\ \left[\begin{matrix}a & b\end{matrix}\right]\left[\begin{matrix}1 & 0\\0 & 0\end{matrix}\right]\left[\begin{matrix}a\\b\end{matrix}\right]=\left[\begin{matrix}a^{2}\end{matrix}\right]\\ \left[\begin{matrix}a & b\end{matrix}\right]\left[\begin{matrix}0 & 1\\0 & 0\end{matrix}\right]\left[\begin{matrix}a\\b\end{matrix}\right]=\left[\begin{matrix}a b\end{matrix}\right]\\ \left[\begin{matrix}a & b\end{matrix}\right]\left[\begin{matrix}0 & 0\\1 & 0\end{matrix}\right]\left[\begin{matrix}a\\b\end{matrix}\right]=\left[\begin{matrix}a b\end{matrix}\right]\\ \left[\begin{matrix}a & b\end{matrix}\right]\left[\begin{matrix}0 & 0\\0 & 1\end{matrix}\right]\left[\begin{matrix}a\\b\end{matrix}\right]=\left[\begin{matrix}b^{2}\end{matrix}\right] 冪算(Exponentiation/Logarithmicion)の射映 極座標系(Polar Coordinate System)(r,θ)上に同心円を描く。 関数y=f(x) 任意の定義域±xに対する値域y 0 y=x^-∞=x/∞ 0 1 y=x^0 1 2 y=x^∞ ∞ import numpy as np import sympy as sp import pandas as pd X1 = np.matrix([ ["y=x^-∞=x/∞","y=x^0","y=x^∞"], ["0","1","∞"]]) x=X1.transpose() df=pd.DataFrame(x,columns=['関数y=f(x)', '任意の定義域±xに対する値域y']) sp.init_printing() org=df.to_html() print(org.replace('\n', '')) 冒頭の定義に従うなら乗法実数群そのものに対応。 指数関数(Exponential Function)と対数関数(Logarithmic Function)概念の導入 不思議にも以下の計算がpythonの冪乗算**では実現出来ないのです。 np.arrayの累乗でRuntime warningが発生する それでここでは複素数演算$xi^n$の実数部だけ取り出す形で計算しています。 【Rで球面幾何学】そもそも複素数Xi(x*(0+1i))はどう振る舞う? $y=\lim_{n \to ∞}\frac{x^n}{n}$あるいは$x=\lim_{n \to ∞}\frac{y^n}{n}$の場合…前者が指数関数(Exponential Function)$y=x^n$、後者が対数関数(Logarithmic Function)$x=y^n(\log_{x}(n))$に対応します。 y=\lim_{n \to ∞}\frac{x^n}{n} = \left\{ \begin{array}{ll} 0 & (-1 \leqq x \leqq 1) \\ ∞ & (x>1) \\ ±∞ & (x<-1) \end{array} \right.\\ x=\lim_{n \to ∞}\frac{y^n}{n} = \left\{ \begin{array}{ll} 0 & (-1 \leqq y \leqq 1) \\ ∞ & (y>1)\\ ±∞ & (y<-1)\\ \end{array} \right.\\ ①n=1の時、y=xとx=yが重なる。 ②n=2の時、偶関数系の$y=\frac{x^2}{2}$ないしは$x=\frac{y^2}{2}$となり、両者は座標(x,y)=(2,2)で交わる。 ③n=3の時、奇関数系の$y=\frac{x^3}{3}$ないしは$x=\frac{y^3}{3}$となり、この調子で両者の交点は座標(x,y)=(1,1)(-1,-1)に近づき続ける。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #複素数 x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 #c1=(-x*(1+0j))**Time_Code[n]/Time_Code[n] #y1=c1.real c2=((x*(1+0j))**Time_Code[n])/Time_Code[n] y2=c2.real #yp= np.power(x, Time_Code[n])/Time_Code[n] #ym=np.power(-x, Time_Code[n])/Time_Code[n] #yp= (np.power(abs(x), Time_Code[n]) * np.sign(x))/Time_Code[n] #ym= (np.power(abs(-x), Time_Code[n]) * np.sign(-x))/Time_Code[n] #ym= -x**Time_Code[n]/Time_Code[n] #描画 plt.plot(y2,x,color="red",marker="o",lw=1,label="x=y^n/n") plt.plot(x,y2,color="blue",marker="o",lw=1,label="y=x^n/n") #plt.plot(-x,ym,color="red",marker="o",lw=1) #plt.plot(x,yp,color="blue",marker="o",lw=1,label="x^n/n") #plt.plot(-x,yp,color="blue",marker="o",lw=1) plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=x^n/n & x=y^n/n ") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(0) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin006.gif", writer="pillow") この0と±∞の峻別は$y=\lim_{n \to ∞}\frac{±x^n}{n}$の形で眺めても興味深いです。 y=\lim_{n \to ∞}\frac{x^n}{n} = \left\{ \begin{array}{ll} 0 & (-1 \leqq x \leqq 1) \\ ∞ & (x>1) \\ ±∞ & (x<-1) \end{array} \right.\\ y=\lim_{n \to ∞}\frac{-x^n}{n} = \left\{ \begin{array}{ll} 0 & (-1 \leqq y \leqq 1) \\ ∞ & (x<-1)\\ \mp ∞ & (x>1)\\ \end{array} \right.\\ ①n=1の時、y=±xとなる。 ②n=2の時、偶関数系の$y=\frac{±x^2}{2}$が重なる。 ③n=3の時、奇関数系の$y=\frac{±x^3}{3}$となり、この繰り返しで両者がそれぞれ上掲の極限に到達する。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #複素数 x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 #c1=(-x*(1+0j))**Time_Code[n]/Time_Code[n] #y1=c1.real c2=((x*(1+0j))**Time_Code[n])/Time_Code[n] y2=c2.real #yp= np.power(x, Time_Code[n])/Time_Code[n] #ym=np.power(-x, Time_Code[n])/Time_Code[n] #yp= (np.power(abs(x), Time_Code[n]) * np.sign(x))/Time_Code[n] #ym= (np.power(abs(-x), Time_Code[n]) * np.sign(-x))/Time_Code[n] #ym= -x**Time_Code[n]/Time_Code[n] #描画 plt.plot(-x,y2,color="red",marker="o",lw=1,label="y=-x^n/n") plt.plot(x,y2,color="blue",marker="o",lw=1,label="y=x^n/n") #plt.plot(-x,ym,color="red",marker="o",lw=1) #plt.plot(x,yp,color="blue",marker="o",lw=1,label="x^n/n") #plt.plot(-x,yp,color="blue",marker="o",lw=1) plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=±x^n/n") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(0) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin601.gif", writer="pillow") $y=\lim_{n \to ∞}(\frac{x}{n})^n=0$あるいは$x=\lim_{n \to ∞}(\frac{y}{n})^n=0$の場合…そう、前者はy=0、後者はx=0に向けて収束するのです。 ①n=1の時、y=xとx=yが重なる。 ②n=2の時、偶関数系の$y=(\frac{x}{2})^2$ないしは$x=(\frac{y}{2})^2$となり、両者は座標(x,y)=(4,4)で交わる。 ③n=3の時、奇関数系の$y=(\frac{x}{3}$)^3ないしは$x=(\frac{y}{3}^3)$となる。この調子で両者の交差座標(x,y)はどんどん無限遠点にに近づき続け、かつその値は原点から0へと近付き続ける。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #複素数 x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 #c1=(-x*(1+0j))**Time_Code[n]/Time_Code[n] #y1=c1.real c2=((x*(1+0j))/Time_Code[n])**Time_Code[n] y2=c2.real #yp= np.power(x, Time_Code[n])/Time_Code[n] #ym=np.power(-x, Time_Code[n])/Time_Code[n] #yp= (np.power(abs(x), Time_Code[n]) * np.sign(x))/Time_Code[n] #ym= (np.power(abs(-x), Time_Code[n]) * np.sign(-x))/Time_Code[n] #ym= -x**Time_Code[n]/Time_Code[n] #描画 plt.plot(y2,x,color="red",marker="o",lw=1,label="x=(y/n)^n") plt.plot(x,y2,color="blue",marker="o",lw=1,label="y=(x/n)^n") #plt.plot(-x,ym,color="red",marker="o",lw=1) #plt.plot(x,yp,color="blue",marker="o",lw=1,label="x^n/n") #plt.plot(-x,yp,color="blue",marker="o",lw=1) plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=(x/n)^n & x=(y/n)^n ") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(4) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin102.gif", writer="pillow") この様な形で原点に接近する様子は$y=\lim_{n \to ∞}(\frac{±x}{n})^n$の形で眺めても興味深いです。 ①n=1の時はy=±xとなる。 ②n=2の時は偶関数$y=(\frac{±x}{2})^2$となりぴったりと重なり合う。 ③n=3の時は奇関数$y=(\frac{±x}{3})^3$となり、この繰り返しでその値が原点から0に合流し続けていく。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #複素数 x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 #c1=(-x*(1+0j))**Time_Code[n]/Time_Code[n] #y1=c1.real c2=((x*(1+0j))/Time_Code[n])**Time_Code[n] y2=c2.real #yp= np.power(x, Time_Code[n])/Time_Code[n] #ym=np.power(-x, Time_Code[n])/Time_Code[n] #yp= (np.power(abs(x), Time_Code[n]) * np.sign(x))/Time_Code[n] #ym= (np.power(abs(-x), Time_Code[n]) * np.sign(-x))/Time_Code[n] #ym= -x**Time_Code[n]/Time_Code[n] #描画 plt.plot(-x,y2,color="red",marker="o",lw=1,label="y=(-x/n)^n") plt.plot(x,y2,color="blue",marker="o",lw=1,label="y=(x/n)^n") #plt.plot(-x,ym,color="red",marker="o",lw=1) #plt.plot(x,yp,color="blue",marker="o",lw=1,label="x^n/n") #plt.plot(-x,yp,color="blue",marker="o",lw=1) plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=(±x/n)^n") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(4) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin203.gif", writer="pillow") 自然指数関数(Natural Exponential Function)と自然対数関数(Natural Logarithm Function)の導入 ここまで辿り着けたなら、ネイピア数exp(1)(2.718282)を求める式$y=\lim_{n \to ∞}(1+\frac{x}{n})^n$やネイピア数exp(-1)(0.3678794)を求める式$y=\lim_{n \to ∞}(1-\frac{x}{n})^n$は目前です。 【Pyrhon演算処理】同心集合①乗法的同心集合とは? 全ての指数関数(Exponential Function)$a^n$は$a^n=e^{n\log{a}}$の変換式を用いて自然指数関数(Natural Exponential Function)$e^n$へと変換可能です その結果こに現れる曲線は自然指数関数(Natural Exponential Function)そのもの。それぞれx=1のところに現れるのがexp(1)およびexp(-1)となり、両者を掛け合わせると乗法単位元exp(0)=1となります。 ①n=1の時には1次方程式y=1±xが現れる。 ②n=2の時は2次方程式$y=(1+\frac{x}{2})^2$が現れる。まだまだ放物線にしか見えない。 ③n=3の時は3次方程式$y=(1+\frac{x}{3})^3$が現れる。おや、様子が… ④n=4の時は4次方程式$y=(1+\frac{x}{4})^4$が現れる。随分とそれっぽく見えてきた。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #複素数 x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 c1=(1-(x*(1+0j))/Time_Code[n])**Time_Code[n] y1=c1.real c2=(1+(x*(1+0j))/Time_Code[n])**Time_Code[n] y2=c2.real #yp= np.power(x, Time_Code[n])/Time_Code[n] #ym=np.power(-x, Time_Code[n])/Time_Code[n] #yp= (np.power(abs(x), Time_Code[n]) * np.sign(x))/Time_Code[n] #ym= (np.power(abs(-x), Time_Code[n]) * np.sign(-x))/Time_Code[n] #ym= -x**Time_Code[n]/Time_Code[n] #描画 plt.plot(x,y1,color="red",marker="o",lw=1,label="y=(1-x/n)^n") plt.plot(x,y2,color="blue",marker="o",lw=1,label="y=(1+x/n)^n") #plt.plot(-x,ym,color="red",marker="o",lw=1) #plt.plot(x,yp,color="blue",marker="o",lw=1,label="x^n/n") #plt.plot(-x,yp,color="blue",marker="o",lw=1) plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=(1±x/n)^n") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(0) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin302.gif", writer="pillow") そして指数関数$y=\lim_{n \to ∞}(1+\frac{x}{n})^n$に対して$x=\lim_{n \to ∞}(1+\frac{y}{n})^n$を考えた場合が対数関数となります。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #複素数 x=np.linspace(-5,5,31,endpoint = True) #描画関数 def liner01(n): plt.cla() #関数計算 c1=(1-(x*(1+0j))/Time_Code[n])**Time_Code[n] y1=c1.real c2=(1+(x*(1+0j))/Time_Code[n])**Time_Code[n] y2=c2.real #yp= np.power(x, Time_Code[n])/Time_Code[n] #ym=np.power(-x, Time_Code[n])/Time_Code[n] #yp= (np.power(abs(x), Time_Code[n]) * np.sign(x))/Time_Code[n] #ym= (np.power(abs(-x), Time_Code[n]) * np.sign(-x))/Time_Code[n] #ym= -x**Time_Code[n]/Time_Code[n] #描画 plt.plot(x,y2,color="red",marker="o",lw=1,label="y=(1+x/n)^n") plt.plot(y2,x,color="blue",marker="o",lw=1,label="x=(1+y/n)^n") #plt.plot(-x,ym,color="red",marker="o",lw=1) #plt.plot(x,yp,color="blue",marker="o",lw=1,label="x^n/n") #plt.plot(-x,yp,color="blue",marker="o",lw=1) plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("y=(1+x/n)^n & x=(1+y/n)^n ") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #liner01(0) #plt.show() ani = animation.FuncAnimation(fig, liner01, interval=50,frames=len(Time_Code)) ani.save("lin401.gif", writer="pillow") 【初心者向け】指数・対数関数の発見とそれ以降の発展について。 自然指数関数 自然対数関数 0 y=exp(x) y=log(x) 1 y=exp(-x) y=log(-x) 2 y=-exp(x) y=-log(x) 3 y=-exp(-x) y=-log(-x) import numpy as np import sympy as sp import pandas as pd X1 = np.matrix([ ["y=exp(x)","y=exp(-x)","y=-exp(x)","y=-exp(-x)"], ["y=log(x)","y=log(-x)","y=-log(x)","y=-log(-x)"]]) x=X1.transpose() df=pd.DataFrame(x,columns=['自然指数関数', '自然対数関数']) sp.init_printing() org=df.to_html() print(org.replace('\n', '')) 実数冪(Real Exponentiation)と虚数冪(Imaginal Exponentiation)の使い分け? ここで急浮上してくるのが自然指数関数/自然対数関数には「実冪系」か「虚冪系」によって振る舞いがかなり変わってくるという問題。 虚数冪(Imaginal Exponentiation)の世界 なんだかんだいってオイラーの公式(Eulerian Formula)$e^{iθ}=cos(θ)+sin(θ)i$が著名なせいで、こちらの方が断然代表格と認識されているのです。それで三角関数とセットで用いられる「回転関数」というイメージがすっかり定着してしまいました。 【Python演算】オイラーの公式と等比数列④「中学生には難しいが高校生なら気付くレベル」? 三角関数の微分(それぞれが+90度の変遷に対応) \cos(θ)\frac{d^n}{dθ^n}=(-\sin(θ),-\cos(θ),\sin(θ),\cos(θ),…)\\ \sin(θ)\frac{d^n}{dθ^n}=(\cos(θ),-\sin(θ),-\cos(θ),\sin(θ),…) 三角関数の積分(それぞれが-90度の変遷に対応) \int \int \int … \int \cos(θ)(dθ)=(\sin(θ),-\cos(θ),-\sin(θ),\cos(θ),…)\\ \int \int \int … \int \sin(θ)(dθ)=(-\cos(θ),-\sin(θ),\cos(θ),\sin(θ),…) 【Python演算処理】等速円運動についての物理学と数学の立場の違い? $\cos(θ)=\frac{e^{θi}+e^{-θi}}{2}$ $\sin(θ)=\frac{e^{θi}-e^{-θi}}{2i}$ $e^{ix}\frac{d^n}{dθ^n}=(i e^{i x}(-\log ix),-e^{ix},-i e^{i x}(\log ix),e^{ix},…)$ $\int \int \int … \int e^{ix}(dθ)=(- i e^{i x}(\log ix),- e^{i x},i e^{i x}(-\log ix),e^{ix},…)$ まぁこの全部が虚冪の説明という有様… 実数冪(Real Exponentiation)の世界 統計学(Statistics)の分野では「平均0を中心に-∞から+∞の範囲に広がる総面積1の分布」、機械学習(Machine Learning)分野では神経エミュレーションの中核たる「0と1の間を滑らかにつなぐ曲線」を生成するのに使われていたりします。人類文明への貢献度、決して低くはないですよね? 正規分布(Normal Distribution) 【初心者向け】正規分布(Normal Distribution)とは何か? {\displaystyle f(x)={\frac {1}{\sqrt {2\pi \sigma ^{2}}}}\exp \!\left(-{\frac {(x-\mu )^{2}}{2\sigma ^{2}}}\right)\quad (x\in \mathbb {R} )} ここではその中枢部たる$e^{-x^2}$の部分の再現のみに集中するものとします。 \lim_{n \to ∞} \int_{x=-∞}^{+∞} ±(1 \pm \frac{-x^2}{n})^n ①N=1の時$y=±(1-x^2)。正負のグラフの交点は自明の場合(Trival Case)として(1,0)(-1,0)となる。 ②N=2の時$±(1-\frac{x^2}{2})^2$ ④N=3の時$±(1-\frac{x^2}{3})^3$ ⑤以降はこの繰り返しで裾野が-∞および+∞に向けてひたすら拡張し続ける。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #放物線x x=np.linspace(-5,5,31,endpoint = True) #描画関数 def parabola01(n): plt.cla() #関数計算 y= (1+(-x**2)/Time_Code[n])**Time_Code[n] #描画 plt.plot(x,-y,color="red",marker="o",lw=1,label="-(1-x^2/n)^n") plt.plot(x,y,color="blue",marker="o",lw=1,label="+(1-x^2/n)^n") plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-5,5]) plt.ylim([-5,5]) plt.xlabel("x") plt.ylabel("y") plt.title("Parabola") ax.legend(loc='lower right') Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14] #parabola01(0) #plt.show() ani = animation.FuncAnimation(fig, parabola01, interval=100,frames=len(Time_Code)) ani.save("parabola06.gif", writer="pillow") シグモイド関数(Sigmoid Function) シグモイド関数 - Wikipedia {\displaystyle \varsigma _{a}(x)={\frac {1}{1+e^{-ax}}}={\frac {\tanh(ax/2)+1}{2}}} ここでは簡略化の為に狭義のシグモイド関数、すなわちゲインを1とした標準シグモイド関数(Standard Sigmoid Function)の再現のみに集中するものとします。 \lim_{n \to ∞} \int_{x=-∞}^{+∞} \frac{1}{1+(1 \pm \frac{-x}{n})^n} 計算上x=-5→5と置いてみます。 \lim_{n=1 \to 16} \int_{x=-5}^{+5} \frac{1}{1+(1 \pm \frac{-x}{n})^n} ①n=1の時、1次関数$\int_{x=-5}^{+5} \frac{1}{2 \mp x}$。これは反比例関数y=1/xをX軸沿いに+2ズラした内容に他なりません。そう、この式自体は$e^0=1$の時に原点の値が丁度$\frac{1}{2}$を通る様に調整されているのです。 ②n=2の時、2次関数$\int_{x=-5}^{+5} \frac{1}{2 \mp x^2/4}$ となります。まだまだ放物線の面影が著しいですね。 ③n=3の時、3次関数$\int_{x=-5}^{+5} \frac{1}{2 \mp x^3/9}$。おや、様子が… ④n=4の時、、2次関数$\int_{x=-5}^{+5} \frac{1}{2 \mp x^2/4}$。大体4分割以降は安定期に入る模様。 import numpy as np import cmath as c import matplotlib.pyplot as plt import matplotlib.animation as animation #figure()でグラフを表示する領域をつくり,figというオブジェクトにする. plt.style.use('default') fig = plt.figure() ax = fig.add_subplot(111,aspect='equal') #放物線x x=np.linspace(-5,5,31,endpoint = True) #描画関数 def sigmoid01(n): plt.cla() #関数計算 c0=(1+(-x*(1+0j))/Time_Code[n])**Time_Code[n] y0=c0.real y=1/(1+y0) #描画 plt.plot(x,-y,color="red",marker="o",lw=1,label="-1/(1+exp(-1))") plt.plot(x,y,color="blue",marker="o",lw=1,label="+1/(1+exp(-1))") plt.axvline(0, 0, 1,color="black",lw=0.5) plt.axhline(0, 0, 1,color="black",lw=0.5) plt.xlim([-6,6]) plt.ylim([-6,6]) plt.xlabel("x") plt.ylabel("y") plt.title("Standard Sigmoid Function") ax.legend(loc='lower right') #Time_Code=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] Time_Code=np.linspace(1,16,32,endpoint = True) #sigmoid01(0) #plt.show() ani = animation.FuncAnimation(fig, sigmoid01, interval=50,frames=len(Time_Code)) ani.save("sig804.gif", writer="pillow") 全体としてネイピア数計算の精度が上がれば上がるほど「有効な曲線の端」が伸びていく印象。同じ傾向は虚冪の場合にも見て取れます。 【Rで球面幾何学】「世界で一番美しい公式」オイラーの等式の罠? そんな感じで、以下続報…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語を簡単に勉強して思う、言語設計の違いについて

GOを簡単に勉強して思う、言語設計の違いについて はじめに この記事は、かる〜〜くGOに触り始めたpython使いが思ったことを書いた記事です。 歴戦のGO使いの方々がご覧になられ「それ、お前が知らないだけだわ」と思った場合、コメントいただけますと大変助かります(できれば、石と匙は投げないでね) Goを触る動機 CとPythonに対するほんの小さな不満 CとPythonは言語として使える(≠使いこなしている)状態の僕ですが、以下の様なほんの小さな不満がありました。 C: 手が遅い僕からすると、開発工数がちょっと重い Python: さっとかけるけど、他の人・環境へのリリースしにくい ①バイナリ形式でリリースしたい(ソースコード出したくない) ②リリース後の環境影響を受けたくない Pythonの方だけ補足すると、①②ともにPyinstallerで一応は解決出来ます。 が、Pyinstaller はバイナリからソースコードへの逆変換自体が出来てしまいます。厳密にいうと、①で本当にやりたい「ソースコードを隠したい」という要望は満たしていません。 Goを触ってみた動機 Goは、言語レベル?でマルチOSへのクロスコンパイルが対応されており、いろいろな環境へのリリースが簡単そう!!という理由です。 (他にもたくさん良いところあると思いますけど、個人的には一番ここに惹かれました!) Goに触ってみた感想 主にPythonやCのライトユーザー視点での感想です。 1. unused系はエラーになる unused.go package main import "fmt" func main(){ test := 1 fmt.Println("hello world") } result(unused.go) ./unused.go:5:2: test declared but not used 端的に書くと、これ 変数の他にもimportしたPackageにしろ、変数の戻り値にしても使っていないと怒られます。(PythonなどではPEP8などで Warningで怒られるレベル) この仕様があると、動作確認前に強制的にレファクタリングが実施され、コード品質は高まります。が、書き捨てるような暫定コードに対しては「ちょっと今テスト中なんだよ……あとに回させろよ……」と思うこともしばしば (おそらく、このコーディング作業自体に問題があると考えた言語設計なのだと思いますが、余計なお世話感は若干あるんですよね^^;) 2. 標準関数の守備範囲が少ない list_match.py manager = ["Alice", "Bob"] if "Bob" in manager: print("Bob is Manager") Pythonでは、List内部に特定の値が含まれているか探す関数は標準関数であり、1行です。それに対して、Goに上記のような標準関数はありません。自作する必要性が出てきます。 その他にリスト内部の最大値を出す、重複削除など、、、 調べると、皆さん困っている様で、いろいろな場所で議論され、様々な関数例は出てきます。が、これだとこういう一般的な関数自体がメンテ対象になってしまいますので、個人的にはしっかりとしたpackageを見つけて置きたいなぁと思いました。 3. Classという概念 GOにもオブジェクトに相当するある様ですが、他のオブジェクト指向言語とはだいぶ概念が違うようです。最たる例としては、継承という概念がないらしいです(継承自体がシステムににおける問題になる可能性を秘めており、その問題を避けるための言語設計らしい) 人によっては、アレルギー症状を発生させる気もしますね。個人的には、Classの中に関数を書いていきたいと思っていたので、構造体の中に関数宣言できないのが若干ムズムズします。 可読性を保つためには、1ファイルの中に1構造体と周辺関数を定義する様なコーディングが強制的に求められている感じがします。 この違和感は悪いことなのか?? 3点出しましたが、GOが悪いと思っているわけではなく、私が悪いのだと考えます。 例えばですが、「unusedあとで消すから」は絶対消しません。GitにCommitしたあとでCI/CDかなにかで指摘が出て「やべ、忘れてた」とかっこ悪い修正Commit飛ばすことになります。それがGOではテスト実行時に前倒しで発見されるので、未然に防がれます。 GOという言語は、私のような「暫定大好き」で「設計をカッチリしない」人間が使っても最低限のコード品質保証が出来るように、一見すると面倒ともとれる安全装置的な言語設計をしているのだと思います。 まとめ 残念ながら、現段階でGOを手持ち言語の最有力とする気にはなれませんでした。気軽に書けるという部分でやはりPythonに魅力を感じてしまいます。 ですが、GO修行は継続しつつ、「GOであったとしても大丈夫なコーディング癖」をつけておこうかなぁと思います。GOの言語思想自体は数十年に渡ってエンジニアの先達がハマってきたコード品質の改良についてのヒントをくれている素晴らしいものと感じました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Regular Contest 124 解説

感想 50分遅れて参加したら、WAが出るわ焦るわで散々な目に遭いました。南無三。 下記ではC問題までを解説しています。D問題以降は解くつもりがありません。 A問題 (以下では、簡略化のために「最も左」の条件があるマスをマスL、「最も右」の条件があるマスをマスR、とします。) マスの間に依存関係はないので、それぞれのマスでありうる数の個数をかけ算すれば答え(ansを998244353で割った余り)が求まります。 まず、マスLまたはマスRとなるマスを考えます。この時、複数の条件が被っているマスは、どの数をそのマスに選んでもいずれかの条件を満たさないので0通りです。よって、それぞれのマスに高々一つの条件がある場合を以降では考えます。 まず、条件のあるマスは自明に1通りなので、ansに1をかけます。次に、条件のないマスがそれぞれ何通りの数を選べるかを考えます。この時、問題の条件より、「そのマスよりも左側にマスLがある」or「そのマスよりも右側にマスRがある」のいずれかを満たす数を選ぶことができます。 ここで、左右が条件に関わるので、左側から順にマスの数をそれぞれ決めます(数え上げの方向を左右のどちらかに統一したい)。この元で、選べる数をnowに保存します。nowの初期化の際には、マスRの個数を格納します。そして、マスLが出てきた場合は選べる数が増えるのでnow++、マスRが出てきた場合は選べる数が減るのでnow--、マスLとマスRのいずれでもない場合はnowをansにかけます。 以上の操作を行ない、nowをansにかける際に998244353で割るようにすることで答えは求まります。 mod=998244353 n,k=map(int,input().split()) ok=set() # rかl check=[0]*n now= 0 for i in range(k): c,_k=input().split() _k=int(_k) ok.add(_k) check[_k-1]=1 if c=="R" else -1 if c=="R": now+=1 if len(ok)!=k: print(0) exit() ans=1 for i in range(n): if check[i]==1: now-=1 elif check[i]==-1: now+=1 else: ans*=now ans%=mod print(ans) B問題 実装を間違えてWAを出しました。実装自体は難しくないとは思います。 数列$b$を並び替えることができるので、$a_0$に固定して考えると$x$の値は$a_0 \oplus b_0$から$a_0 \oplus b_{n-1}$の高々$n$通りになります。$x$の値がわかっている時、XORの性質である「$a \oplus b=c \leftrightarrow a \oplus c=b$」(対称差と呼ぶらしい)を利用すれば、$a_i \oplus x=b^{'}_i$として$x$を良い数とする$b^{'}_i$を求めることができます。また、$b_i=b^{'}_i$が任意の$i$で成り立つときに$x$は良い数となるため、$b_i$,$b^{'}_i$をそれぞれ昇順ソートして一致するかを確かめます。 また、以上を行なって良い数$x$を昇順で列挙しますが、同じ数が複数含まれないように注意が必要です。Goにはsetがないので、WAを出しました。なぜPythonを使わなかったんでしょうか。 // 22:00~22:15 package main import ( "bufio" "fmt" "os" "sort" ) func main() { scin := bufio.NewReader(os.Stdin) prout := bufio.NewWriter(os.Stdout) defer prout.Flush() // code var n int fmt.Fscan(scin, &n) var a []int = make([]int, n) var b []int = make([]int, n) for i := 0; i < n; i++ { fmt.Fscan(scin, &a[i]) } for i := 0; i < n; i++ { fmt.Fscan(scin, &b[i]) } var y []int for i := 0; i < n; i++ { y = append(y, a[0]^b[i]) } sort.Slice(y, func(i, j int) bool { return y[i] < y[j] }) var ans []int for _, x := range y { var nowa []int = make([]int, n) var nowb []int = make([]int, n) copy(nowa, a) copy(nowb, b) for i := 0; i < n; i++ { nowa[i] ^= x } sort.Slice(nowa, func(i, j int) bool { return nowa[i] < nowa[j] }) sort.Slice(nowb, func(i, j int) bool { return nowb[i] < nowb[j] }) f := true for i := 0; i < n; i++ { if nowa[i] != nowb[i] { f = false break } } if f { if len(ans) == 0 { ans = append(ans, x) } else { if ans[len(ans)-1] != x { ans = append(ans, x) } } } } fmt.Fprintln(prout, len(ans)) if len(ans) != 0 { for i := 0; i < len(ans); i++ { fmt.Fprintln(prout, ans[i]) } } } C問題 コンテスト中は、初めの考察段階で「約数を利用しよう」と決めていたのに、YouTubeを見始めて帰ってきたら謎の愚直な嘘解法を生み出していました。もったいない。 本解と同様の方針が良いです。最大公約数はそこまで候補がないだろうという予測はつくので、$X$と$Y$の候補を列挙することをまずは考えます。また、最大公約数は数を選ぶにつれて広義単調減少するので、一番最大公約数が大きい状態である1つのみのカードパックを選んだ時を考えます(一番制約が緩い状態)。このとき、1つのカードパックである$a_0,b_0$において、$a_0$を赤袋に$b_0$を青袋に入れても一般性を失わないため、この仮定の元で考えます。 まず、$X$についてですが、赤袋に入れた任意の数の最大公約数であることから、先ほどの仮定により$a_0$の約数であることが必要条件です。また、$Y$についても$b_0$の約数であることが必要条件です。つまり、$X$と$Y$の組の候補を二重ループにより列挙することが可能です。それぞれのループにおいて、「$a_i$が$X$で割り切れ、$b_i$が$Y$で割り切れる」or「$a_i$が$Y$で割り切れ、$b_i$が$X$で割り切れる」、を任意の$i$で成り立つかを試せば、その$X$と$Y$の組が存在しうるかを確かめられます。この元で$X,Y$の最小公倍数を求めていき、その中での最大値を答えとして求めます。 下記では、こちらの記事の約数列挙のコードを使用しました。 from math import gcd def lcm(x,y): return x*y//gcd(x,y) def make_divisors(n): divisors=[] for i in range(1,int(n**0.5)+1): if n%i==0: divisors.append(i) if i!=n//i: divisors.append(n//i) #約数の小さい順にソートしたい場合 #divisors.sort() #約数の大きい順にソートしたい場合 #divisors.sort(reverse=True) return divisors n=int(input()) ab=[list(map(int,input().split())) for i in range(n)] ans=0 for i in make_divisors(ab[0][0]): for j in make_divisors(ab[0][1]): for k in range(1,n): if (ab[k][0]%i==0 and ab[k][1]%j==0)or(ab[k][0]%j==0 and ab[k][1]%i==0): pass else: break else: ans=max(ans,lcm(i,j)) print(ans)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む