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

【機械学習】ニューラルネットワークで競輪予想してみた(データセット準備編)

はじめに 本記事は自身がニューラルネットワークで競輪予想をしてみた一連の流れをまとめたものになります. 全行程を一気にまとめると長くなりそうだったので今回は前半部分のデータセットの準備までをまとめました. 環境 Python 3.8.11 目次 問題設定 データ収集 データ前処理 条件設定 そもそも競輪とは 1レース,7〜9人くらいでレースを行います.上位3人をいろんなかけ方で予想するといったものです. 競輪のルール,かけ方などはこちらに詳しく乗ってます. 条件設定 今回は問題を簡単化して,ある選手が3着以内に入るかどうかの二値分類問題にしようと思います! 特徴量 今回は特徴量として以下のような要素を選定しました. * 予想 ••• 新聞記者による予想 * 好気合 ••• 初走以降で動きが好気合と評価した選手 * 総評 ••• 新聞本紙で設定された選手のパワーランク(1~20)小さい方が強い * 枠番 ••• 枠の番号 * 年齢 ••• 選手の年齢 * 級班 ••• 選手の階級(A3~SS) * 脚質 ••• 選手の走りのタイプ * ギヤ倍数 ••• 自転車の前と後ろについているギアの比率 * 競走得点 ••• 出場したレースのランク•着順によって決まる点数(直近4ヶ月の合計) * 同走路年間勝利度数(1着,2着,3着,着外) ••• 同じレース上の成績 なお,今回は選手単体での特徴量しか集めてません,開催場所や天候などのレース情報であったり,選手同士の関係などを考慮したモデルはまた別の機会にやってみようと思います. データ収集 条件が決まったところで早速データを集めていきたいと思います.今回はpythonのwebスクレイピングを使って集めたいと思います. webスクレイピングとは  詳しい説明は割愛しますが,プログラミングを使ってwebサイトの情報(HTML)を取ってきて加工したりすることを言います.  なお,スクレイピングはサイトの規約などを守ったうえで行うようにしましょう.注意事項などはこちらに詳しくまとめられています. 使用するサイト 今回は数ある競輪サイトの中からRakuten Kドリームズさんを選びました.選んだ理由としては,取得したい出走表のデータがまとまっていて集めやすいと感じたためです. 1. URLの取得 Kドリームズさんのサイト内で,先程設定した特徴量が取得できるテーブルが乗っているのが以下のページになります. 2021年9月9日の青森競輪での1レース目のレース情報 https://keirin.kdreams.jp/aomori/racedetail/1220210909010001/ これが1レース分の情報で,これを大量に集めたいのですが,URLを一つ一つ集めるのは面倒なのでURL内に含まれる情報をうまく利用しようと思います. URLを見た感じ,以下のような情報が含まれています. レース場 /aomori/ レースID /1220210909010001/ レースIDに関しては/レース場ID 年月日 何日目か 何レース目か/といった情報が並んでいるようです. この部分をうまく変えていくことでURLを一気に取得したいと思ったのですが... 競輪は毎日違うレース場で開催されているためどの日にどこのレース場でレースが行われたかという情報がないと集められないことに気づきました. そこで,いろいろと模索していたところ以下のページを発見しました. https://keirin.kdreams.jp/racecard/2021/09/09/ 日付だけでその日に行われるレースがわかる!!これでいける!! したがって,まずは?を必要日数分集めて,それをもとにその日行われたレースのURLを一気に集めたいと思います. get_Data.ipynb def createURL(month, day): url = 'https://keirin.kdreams.jp/racecard/2021/' + str(month).zfill(2) + '/' + str(day).zfill(2) + '/' return url seedURLs = [ createURL(i, j) for i in range(4, 8, 1) for j in range(1, 30, 1)] seedURLs こんな感じで2021年4月1日~7月29日までのレースURLが乗ったページのURLをゲットできました. つぎはここにアクセスして1レースごとのURLをゲットしていきたいと思います. その前にここからスクレイピングに入っていくのでライブラリをインポートしたいと思います. get_Data.ipynb from bs4 import BeautifulSoup import requests import tqdm.notebook as tqdm import time スクレイピングするときは大体このへん入れとけばオッケー 続いてURLを取得していきます.先ほど取得したURLに一つ一つアクセスしてhtmlを取得し,その中から各レースのURLを抜き出していきます. 取得したURLはわかりやすいようにレースIDをキーとした辞書に入れていきます. get_Data.ipynb def get_race_urls(sourceURLs): #URLを格納するための辞書を定義 race_urls = {} #tqdmを使うことでループの進度が表示される for sourceURL in tqdm.tqdm(sourceURLs): try: #リクエストを作成 req = requests.get(sourceURL) #htmlデータを取得 soup = BeautifulSoup(req.content, 'html.parser') #1秒待機 time.sleep(1) #レース情報のページのURLを取得する race_html = soup.find_all('a', class_='JS_POST_THROW') for html in race_html: url = html.get('href') #"一覧"のURL以外を取得 if 'racedetail' in url: race_id = re.sub(r'\D', '', url) race_urls[race_id] = url except: break return race_urls race_urls = get_race_urls(seedURLs) race_urls これで取得したかったURLがゲットできました! 2. データの取得 URLが手に入ったのであとはここにアクセスして必要なデータを取得していきます. get_Data.ipynb main_colum = ['予想', '好気合', '総評', '枠番', '車番', '選手名府県/年齢/期別', '級班', '脚質', 'ギヤ倍数', '競走得点', '1着', '2着', '3着', '着外'] result_colum = ['予想', '着順', '車番', '選手名', '着差', '上り', '決まり手', 'S/B', '勝敗因'] def scrape_race_result(race_urls, pre_race_results={}): #取得途中のデータを途中から読み込む race_results = pre_race_results for race_id, url in tqdm.tqdm(race_urls.items()): if race_id in race_results.keys(): continue try: #ページ内ののテーブル(表)のhtmlを取得 main = pd.read_html(url) #レース情報(特徴量データ)のテーブルを取得 df = main[4][:-1] df.columns = main_colum #レース結果(教師データ)のテーブルを取得 result_table = main[-2] result_table.columns = result_colum df_result = result_table.loc[ : , ['着順', '車番']] #文字列型に変換 df = df.astype(str) df_result = df_result.astype(str) #特徴量データと教師データを一つにまとめる df = pd.merge(df_result, df, on='車番', how='left') race_results[race_id] = df #1秒待機 time.sleep(1) except IndexError: print('IndexError: {}', url) continue except KeyError: print('keyerror: {}', url) continue except ValueError: print("ValueError: {}", url) continue except : traceback.print_exc() break return race_results results = scrape_race_result(race_urls, results) 変数resultsにはレースIDをキーとしたpandasのデータフレームが格納されています.以下のようにレースIDを指定して上げるとしっかりとデータフレームが取得できていることがわかります. 3. データを結合する 現状ではデータがレースごとに別れているのですべてを結合して一つのデータにしたいと思います. get_Data.ipynb #各レースデータの行名をレースIDに変更 for key in results.keys(): results[key].index = [key]*len(results[key]) #全データを結合 race_results = pd.concat([results[key] for key in results.keys()], sort=False) race_results このようにデータがひとまとまりになりました. 4. データを保存 最後にデータをpickleファイルに保存しましょう. get_Data.ipynb race_results.to_pickle("data/race_data.pkl") データ前処理 続いては先程保存したデータを前処理していこうと思います.具体的に以下のようなことを行います. * 列を分割 * ダミー変数化 * 文字列型の値を数値型に変換 * 正規化 * 特徴量データと教師データに分割 ここからは別ファイルで行っていきます. data_cleaning.ipynb import pandas as pd race_data = pd.read_pickle("data/race_data.pkl") これで準備完了です. 1. 列を分割 収集したデータの中で選手名/年齢/期別という列に注目すると,3つの列が一つにまとまっている事がわかります. これを分割していきたいと思います. data_cleaning.ipynb #concatメソッドで列を分割し,もとの列を削除 race_data = pd.concat([race_data, race_data["選手名府県/年齢/期別"].str.split("/", expand=True)], axis=1).drop("選手名府県/年齢/期別", axis=1) #選手名の列を削除 race_data = race_data.drop(0, axis=1) #年齢と期別の列名を変更 race_data = race_data.rename(columns={1: "年齢", 2: "期別"}) 分割されて新しくなった列が最後に追加されています.これで完了です. 2. ダミー変数化 ダミー変数かとは,質的なデータを量的なデータに変換することを言います. 今回のデータでいうと予想,好気合,級班,脚質がダミー変数化の対象となります. ダミー変数化は調べてみるとpandasのget_dummiesメソッドで簡単にできるようです.しかし,こちらの記事にもある通り,機械学習の用途で使う際は注意が必要みたいです. data_cleaning.ipynb #ダミー変数の対象と,カテゴリーを定義 dummy_targets = {"予想": ["nan", "×", "▲", "△", "○", "◎", "注"], \ "好気合": ["★"], \ "脚質": ["両", "追", "逃"], \ "級班": ["A1", "A2", "A3", "L1", "S1", "S2", "SS"] } #定義したカテゴリーを指定しておく for key, item in dummy_targets.items(): race_data[key] = pd.Categorical(race_data[key], categories=item) #ダミー変数化されたデータフレームを格納するリストと削除する列のリストを定義 dummies = [race_data] drop_targets = [] #ダミー変数化してdummiesに代入 for key, items in dummy_targets.items(): dummy = pd.get_dummies(race_data[key]) dummies.append(dummy) drop_targets.append(key) #ダミー変数化されたデータフレームを大元のデータフレームに結合 race_data = pd.concat(dummies, axis=1).drop(drop_targets, axis=1) これにて完了です. 3. 文字列型の値を数値型に変換 モデルにデータを入れるときは,数値型でないといけないので全データを数値型に変換していきたいと思います. 各列ごとの現在の型は, race_data.dtypes で確認できます. pandasのデータ型一覧:https://pbpython.com/pandas_dtypes.html object型をfloat型に変更したいのですが,変換でエラーになる部分があるため個別に手直ししてから変換していきます. data_cleaning.ipynb #落車などで順位が出なかった部分を9位として変換 race_data = race_data.replace(["失", "落", "故", "欠"], 9) #ギヤ倍数の表示がおかしい部分を変換 race_data["ギヤ倍数"] = race_data["ギヤ倍数"].map(lambda x: x[:4] if len(x)>4 else x) #期別に含まれる欠車の文字を除外 race_data["期別"] = race_data["期別"].map(lambda x: x.replace(" (欠車)", "") if "欠車"in x else x) #着順の列を3着以内は1,それ以外は0に変換 race_data["着順"] = race_data["着順"].map(lambda x: 1 if x in ["1", "2", "3"] else 0) #全データをfloat型に変換 race_data = race_data.astype("float64") これですべてのデータが数値型のデータに変換できました. 4. 正規化 正規化とはデータの値を何らかの方法で0~1の範囲で表現することです. 要はそのままのデータだと,値が大きいものが重みに関係なく重要視されてしまうのでそれを避けようと言うことです. 詳しくは以下の記事が参考になります. https://qiita.com/yShig/items/dbeb98598abcc98e1a57 data_cleaning.ipynb #最大値が1最小値が0になるように正規化 def minmax_norm(columns): df = race_data[columns] for column in columns: race_data[columns] = (df - df.min()) / (df.max() - df.min()) minmax_columns = ["総評", "枠番", "ギヤ倍数", "競走得点", "1着", "2着", "3着", "着外", "年齢", "期別"] minmax_norm(minmax_columns) 今回は最大値が1,最小値が0となるように正規化を行いました. 5. 特徴量データと教師データに分割 data_cleaning.ipynb #特徴量データと教師データに分割 race_y = race_data['着順'] race_x = race_data.drop('着順', axis=1) race_x = race_x.loc[:, ['車番', '総評', '枠番', 'ギヤ倍数', '競走得点', \ '1着', '2着', '3着', '着外', "年齢", "期別", 'nan', \ '×', '▲', '△', '○', '◎', '注', '★', '両', '追', '逃', \ 'A1', 'A2', 'A3', 'L1', 'S1', 'S2', 'SS']] #データを保存 race_x.to_pickle("data/race_x.pkl") race_y.to_pickle("data/race_y.pkl") これでようやくデータセットが完成しました. まとめ 無事に目的のデータセットを得ることができました! 今回初めてスクレイピングを使ったデータ収集や前処理の流れを一通り経験して,わからないことだらけでしたが,いい経験になりました.最後までお付き合いいただきありがとうございました! モデル作成・学習編はまた後日記事にする予定です. 参考 競馬予想で始めるデータ分析・機械学習 ディープラーニングで競馬予想 機械学習の初心者がpythonで競馬予測モデルを作ってみた 競馬予想 機械学習(LightGBM)で回収率100%超えたと思ったら、やらかしてた話 【Python】DataFrameで複数行・列をまとめて削除する ゆるふわPandasチートシート
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テスト投稿(線形回帰分析してみる)

1.今回の目的 はじめまして。Chimoと申します。 今回Qiitaに初めて記事を書いてみようと思います。 会社での調べものやE資格の勉強の際はいつもQiitaを除いており、皆様の記事に大変お世話になりました。 私ごときが投稿するのは非常に緊張しますが、頑張ります。 記事に関してご意見やご感想ございましたら、勉強になりますのでコメントいただけますと幸いです。 さて、今回は記事を書く練習を兼ね、Boston Housingのデータセットを用いて 住宅価格の線形回帰モデルを作ることを目標にしたいと思います。 2.データセット読み込み「Boston Housing」 scikit-learnに付属するデータセットから、Boston Housingを読み込みます。 import numpy as np import matplotlib.pyplot as plt #%matplotlib inline で アウトプット行にグラフを表示できる様になるらしい %matplotlib inline from sklearn.datasets import load_boston 以下の様に変数XとYに読み込ませます。 boston = load_boston() X=boston.data #各特徴量 Y=boston.target #目的変数 fname=boston.feature_names #各特徴量の名称 X,Y,fnameのデータを取り出すと、ちゃんとデータが入っていることが確認できます。 X[:,1] #スライスで0行目を表示 (結果) array([ 18. , 0. , 0. , 0. , 0. , 0. , 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 0. , 0. , 0. , 0. , 0. , ~中略~ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]) Y[:5] #0~4個目までの要素を表示 (結果) array([24. , 21.6, 34.7, 33.4, 36.2]) fname #各特徴量の名前 (結果) array(['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT'], dtype='<U7') 3.関数作成 3.1 散布図表示 以下の通り散布図を表示させるための関数を作成します。 def plotdata(X,Y,num): plt.xlabel(fname[num],fontsize=14) plt.ylabel('住宅価格[千ドル]',fontsize=14,fontname="MS Gothic") plt.title(fname[num] + 'と住宅価格[千ドル]の関係',fontsize=14,fontname="MS Gothic") plt.scatter(X,Y,c='b') plt.draw() return 作った関数で"ZN"(*0)と価格の散布図を表示させると以下の通りです。 plotdata(X[:,1],Y,1) (結果) (*0)ZNは25,000 平方フィート以上の住居区画の占める割合 3.2 仮説 次の式で仮説:h(線形回帰モデル)を定義します。 h = X * a + b ここでXは入力データ,a,bはパラメータを示します。 def h(X,a,b): h = X * a + b return h 3.3 目的関数 以下の通り目的関数:Jを実装します。(数式の載せ方がわからない・・w) def J(h,Y): m = Y.size J = np.sum(( h - y )**2) / (2*m) return J 4.線形回帰の学習 4.1 用いる特徴量 13個の特徴量を散布図で見ると"RM"(*1)が住宅価格と相関してそうなので, 適当ですが特徴量はこちらを選ぶことにします。 plotdata(X[:,5],Y,5) (結果) (*1)RMは住居の平均部屋数 4.2 学習の実施 3で作成した仮説と目的関数の関数を用いて,最急降下法によりパラメータa,bを学習させます。 ここで再急降下法は目的関数Jを各パラメータで偏微分して求めます(数式の載せ方がry)。 結果から,学習したパラメータa,bと,コスト関数の下がり具合を示したグラフが得られます。 グラフによると,100~125回くらい学習すれば十分な様ですね。 m = X.size c = [] #costの累計に用いる配列をクリア alpha = 0.01 #学習率 a = 0 #パラメータを初期化 b = 0 for i in range(200): #200回学習 H = h(X[:,5], a, b) #仮説にRM,パラメータ初期値を代入 J = j(H,Y) #目的関数の値を計算 c.append( J )#目的関数の値を累計 a = a - alpha * np.sum(( H - Y) * X[:,5]) / m # 最急降下法によりパラメータを学習 b = b - alpha * np.sum( H - Y) / m plt.plot(c[0:200])#目的関数の値の類型をプロット print('パラメータa:',a) print('パラメータb:',b) (結果) パラメータa: 3.567989796463954 パラメータb: 0.4941873961329116 5.学習結果のプロットと考察 5.1 学習結果のプロット 4.2にて学習した仮説h_resultをRMの散布図上に重ねてみます。 h_result = a * X[:,5] + b plotdata(X[:,5],Y,5) plt.xlim([0,10]) plt.ylim([0,52]) plt.plot(X[:,5],h_result, c='red') (結果) 学習回数を10→20→・・・90→100→200(おまけ)に連れ、仮説h_resultは以下gifの様にうごきました。 5.2 考察 RMのプロットに対し、感覚的にはh_resultの傾きはもう少しきつい方がよさそうに思えます。 学習データの前処理を行わずに学習させたので、外れ値の影響を受けているものと推測しています。 6.感想 初めて記事を書いてみましたが、とても楽しかったです。 数式の載せ方については調べておきたく思います・・・ 次回以降でRMの前処理後の回帰や住宅価格の予測,重回帰等にも挑戦してみたいと思います。 以 上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

線形回帰分析してみる

2021.09.13 タイトル変更 1.今回の目的 はじめまして。Chimoと申します。 今回Qiitaに初めて記事を書いてみようと思います。 会社での調べものやE資格の勉強の際はいつもQiitaを除いており、皆様の記事に大変お世話になりました。 私ごときが投稿するのは非常に緊張しますが、頑張ります。 記事に関してご意見やご感想ございましたら、勉強になりますのでコメントいただけますと幸いです。 さて、今回は記事を書く練習を兼ね、Boston Housingのデータセットを用いて 住宅価格の線形回帰モデルを作ることを目標にしたいと思います。 2.データセット読み込み「Boston Housing」 scikit-learnに付属するデータセットから、Boston Housingを読み込みます。 import numpy as np import matplotlib.pyplot as plt #%matplotlib inline で アウトプット行にグラフを表示できる様になるらしい %matplotlib inline from sklearn.datasets import load_boston 以下の様に変数XとYに読み込ませます。 boston = load_boston() X=boston.data #各特徴量 Y=boston.target #目的変数 fname=boston.feature_names #各特徴量の名称 X,Y,fnameのデータを取り出すと、ちゃんとデータが入っていることが確認できます。 X[:,1] #スライスで0行目を表示 (結果) array([ 18. , 0. , 0. , 0. , 0. , 0. , 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 0. , 0. , 0. , 0. , 0. , ~中略~ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]) Y[:5] #0~4個目までの要素を表示 (結果) array([24. , 21.6, 34.7, 33.4, 36.2]) fname #各特徴量の名前 (結果) array(['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT'], dtype='<U7') 3.関数作成 3.1 散布図表示 以下の通り散布図を表示させるための関数を作成します。 def plotdata(X,Y,num): plt.xlabel(fname[num],fontsize=14) plt.ylabel('住宅価格[千ドル]',fontsize=14,fontname="MS Gothic") plt.title(fname[num] + 'と住宅価格[千ドル]の関係',fontsize=14,fontname="MS Gothic") plt.scatter(X,Y,c='b') plt.draw() return 作った関数で"ZN"(*0)と価格の散布図を表示させると以下の通りです。 plotdata(X[:,1],Y,1) (結果) (*0)ZNは25,000 平方フィート以上の住居区画の占める割合 3.2 仮説 次の式で仮説:h(線形回帰モデル)を定義します。 h = X * a + b ここでXは入力データ,a,bはパラメータを示します。 def h(X,a,b): h = X * a + b return h 3.3 目的関数 以下の通り目的関数:Jを実装します。(数式の載せ方がわからない・・w) def J(h,Y): m = Y.size J = np.sum(( h - y )**2) / (2*m) return J 4.線形回帰の学習 4.1 用いる特徴量 13個の特徴量を散布図で見ると"RM"(*1)が住宅価格と相関してそうなので, 適当ですが特徴量はこちらを選ぶことにします。 plotdata(X[:,5],Y,5) (結果) (*1)RMは住居の平均部屋数 4.2 学習の実施 3で作成した仮説と目的関数の関数を用いて,最急降下法によりパラメータa,bを学習させます。 ここで再急降下法は目的関数Jを各パラメータで偏微分して求めます(数式の載せ方がry)。 結果から,学習したパラメータa,bと,コスト関数の下がり具合を示したグラフが得られます。 グラフによると,100~125回くらい学習すれば十分な様ですね。 m = X.size c = [] #costの累計に用いる配列をクリア alpha = 0.01 #学習率 a = 0 #パラメータを初期化 b = 0 for i in range(200): #200回学習 H = h(X[:,5], a, b) #仮説にRM,パラメータ初期値を代入 J = j(H,Y) #目的関数の値を計算 c.append( J )#目的関数の値を累計 a = a - alpha * np.sum(( H - Y) * X[:,5]) / m # 最急降下法によりパラメータを学習 b = b - alpha * np.sum( H - Y) / m plt.plot(c[0:200])#目的関数の値の類型をプロット print('パラメータa:',a) print('パラメータb:',b) (結果) パラメータa: 3.567989796463954 パラメータb: 0.4941873961329116 5.学習結果のプロットと考察 5.1 学習結果のプロット 4.2にて学習した仮説h_resultをRMの散布図上に重ねてみます。 h_result = a * X[:,5] + b plotdata(X[:,5],Y,5) plt.xlim([0,10]) plt.ylim([0,52]) plt.plot(X[:,5],h_result, c='red') (結果) 学習回数を10→20→・・・90→100→200(おまけ)に連れ、仮説h_resultは以下gifの様にうごきました。 5.2 考察 RMのプロットに対し、感覚的にはh_resultの傾きはもう少しきつい方がよさそうに思えます。 学習データの前処理を行わずに学習させたので、外れ値の影響を受けているものと推測しています。 6.感想 初めて記事を書いてみましたが、とても楽しかったです。 数式の載せ方については調べておきたく思います・・・ 次回以降でRMの前処理後の回帰や住宅価格の予測,重回帰等にも挑戦してみたいと思います。 以 上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt で WordCloud を作る・そのためのデータを Python で作成する

成果物 以前、こちらのサイトを参考にさせていただき、 こんなアプリを作りました。 これを応用しながら、現在開発中のアプリにも、WordCloud の表示を追加しました。 こちらは、一例ですが、色んな方の専門家会議等での発言に基づいて、WordCloud としてまとめています。 アプリケーション方式 このアプリ自体は、フロントを Nuxt で作って、Netlify にデプロイしています。 API を、Django REST Framework で作り、Heroku にデプロイしています。 DB は、Heroku と連携して使える、ClearDB MySQL に作成しています。 今回作ろうとしているのは、専門家会議の構成員一人一人についてのWordCloudです。 これを、リアルタイムで生成するのは無理だと思うので、上記アプリ構成外で、データ生成(ワードごとの重み付けデータ)を行います。 上記 sake-pairing は、定期バッチで生成しています。なぜなら、集計元のデータがTwiiterのツイートであり、これはリアルタイムで変わっていくものだからです。 しかし、今回は、集計元データは今の所自分で登録しているデータですので、データ登録に合わせて生成すれば問題ありません。 もう一点、ClearDB の制限が意外ときつい。(3600query/hour?) 最初、ローカルのプログラムでClearDBに接続していましたが、それだと接続制限にかかってしまいました。 ですので、ローカル開発環境でファイルを生成し、github にコミットすることでデプロイされ、Nuxt アプリ側からは、そのファイルを読み込んでWordCloud を生成する構成とします。 Python による、形態素解析、ワードカウント、ファイル生成 結論としては、こんなプログラムになりました。 create_wc.py import json import urllib.request import tempfile from janome.tokenizer import Tokenizer import pandas as pd import os folder_name = "./wc" def main(): try: url = "http://localhost:8000/api/person/" res = urllib.request.urlopen(url) data = json.loads(res.read().decode('utf-8')) # 実行結果(json)をdata変数に格納 except urllib.error.HTTPError as e: print('HTTPError: ', e) except json.JSONDecodeError as e: print('JSONDecodeError: ', e) for person in data: print(person['id']) # idのみ参照 try: url = "http://localhost:8000/api/meeting_speech/" params = { 'person': person['id'], } req = urllib.request.Request('{}?{}'.format(url, urllib.parse.urlencode(params))) # res = urllib.request.urlopen(req) with urllib.request.urlopen(req) as res: speech_data = json.loads(res.read().decode('utf8')) except urllib.error.HTTPError as e: print('HTTPError: ', e) except json.JSONDecodeError as e: print('JSONDecodeError: ', e) if len(speech_data) > 0: speech = [] # print(speech_data) for skey in speech_data: # print(skey) speech.append(skey['speech']) out = gen_wordcloud(speech) out.to_json(folder_name + '/' + person['id'] + '.json', orient = 'records', force_ascii=False) def gen_wordcloud(texts): words = [] for text in texts : words.extend(counter(text)) sr = pd.Series(words) counts = sr.value_counts() df = pd.DataFrame(counts.rename_axis('name').reset_index(name='value')) return df def counter(text): if not text: return '' t = Tokenizer() words = [] try: tokens = t.tokenize(text) for token in tokens: #品詞から名詞だけ抽出 pos = token.part_of_speech.split(',')[0] if (pos == '名詞') or (pos == '動詞') or (pos == '形容詞'): if token.base_form not in '#, [], する, (), ある, @, これ, こと, よう, いる, なる, さん, いう, ない, おる, れる, の, てる, もの, とこ, 料理, やる, つく, やつ, しまう, できる, それ, くる, とき, いく, せる, ところ, ため, たち, いく, くれる, わけ, 思う, いい, いただく, 行う, &': words.append(token.base_form) except: print('error!', text) return words if __name__ == '__main__': main() 元データ(今回は発言データ)取得 直接DBからの取得ではなく、既存API経由で取得します。 人ごとにファイルを作成しますので、 http://localhost:8000/api/person/ で、人情報を取得します。 人のループで、 http://localhost:8000/api/meeting_speech/ で発言情報を取得します。 どちらもJSONで取得するので、 with urllib.request.urlopen(req) as res: speech_data = json.loads(res.read().decode('utf8')) の部分で、データとして扱える形を作ります。 speech = [] に文字列配列を作った上で、これを、gen_wordcloud に渡します。 形態素解析 gen_wordcloud から、すかさず、counter に渡して、単語ごとに分割します。 janome.tokenizer、何もわからなくても、勝手に分割して、品詞判別までしてくれる。 素晴らしいですね。 で、token.base_form に単語が入っているので、それを配列に突っ込んでいくわけですが、一部、意味のない単語を排除していきます。結果を見ながら、どんどん足していくわけですが、こういうの上手くできると良いなと思います。 出現数カウント counter で単語一覧ができたので、pandas でカウントします。 これは、pandas の素晴らしさで簡単にできるわけですが、これをファイルにするところは、ちょっと悩みました。 ただ、これも素晴らしいことに、padas の DataFrame には、to_json という操作ができる。 引数、force_ascii=False はtrueじゃなくてfalse というところに気をつければ、できるというわけです。 また、その前に、 df = pd.DataFrame(counts.rename_axis('name').reset_index(name='value')) とすることで、単語の列に「name」、カウントの列に「value」という名前をつけることができます。 Nuxt での、WordCloud 生成 データができたらNuxt側で読み込みます。 最初、axios で読み込むサンプルを見て、ただ、nuxt.config.ts で nuxt.config.ts axios: { baseURL: process.env.BASE_URL || 'http://localhost:8000/api/', }, のように定義していて、自動的に、API側を見にくようになっていた、すなわち、上記構成だと、Heroku 側に醜ようになっていたので、そちらにファイルを置こうとしました。 そのために、まず、staticfiles に置くのに苦労しましたが、それに成功しても、CORS に引っかかるという状況だったので、Nuxt 側の静的ファイルとして置くことにしました。 (余談)Django における静的ファイルの配置 最初、ファイルを「staticfiles」に直接置いても参照できず、 python manage.py collectstatic しても、反応しないなと悩んでいたのですが、アプリ側フォルダ内に、「static」フォルダを作成し、そこにファイルを置いた上で、collectstatic をすると、staticfiles にファイルがコピーされて、三勝可能になるのでした。 assets フォルダの参照 では、実際に行った方式です。Nuxt 構成の、assets フォルダに、ファイルを置き、それを参照するようにします。 こちらも、結論としては、このようになります。 <template> ・・・ <wordcloud :data="words" name-key="name" value-key="value" :show-tooltip="false" /> ・・・ </template> <script lang="ts"> // 個人発言リスト import { reactive, computed, toRefs, useFetch, useContext, defineComponent } from '@nuxtjs/composition-api'; import { useMeetingSpeech } from '@/compositions'; // import jsonData from '@/assets/wordcloud/1e987981-415a-4e50-ab5f-2a9e35243056.json' // import jsonData from `@/assets/wordcloud/${props.personId}.json` export default defineComponent({ name: 'PersonSpeechList', props: { personId: { type: String, required: true, }, }, setup(props, { root }) { console.log('personId', props.personId); const { state: meetingSpeechState, getMeetingSpeechList } = useMeetingSpeech(); const itemsPerPageArray = [4, 8, 12]; const state = reactive({ search: '', filter: {}, sortDesc: false, words: [ { name: "発言", value: "100" }, { name: "Wordcloud", value: "20" }, { name: "議事録", value: "30" }, { name: "政府会議体", value: "40" }, ], }); const page = 1; const itemsPerPage = 60; const sortBy = 'name'; const keys = [ 'meeting_date', 'name']; try { const jsondata = require(`~/assets/wordcloud/${props.personId}.json`) // console.log('jsondata', jsondata); if (jsondata) { state.words = jsondata } } catch { console.log('no jsondata'); } const fetchData = async (offset = 0, personId = '') => { console.log('personId', personId); await getMeetingSpeechList({ offset: offset, person: personId }); // const jsondata = await import(`~/assets/wordcloud/${props.personId}.json`) // const jsondata = await fetch(`/wordcloud/${props.personId}.json`).then(res => // res.json() // ) // console.log('jsondata', jsondata); // state.words = jsondata }; const { fetchState } = useFetch(() => fetchData(0, props.personId)); return { itemsPerPageArray, ...toRefs(state), page, itemsPerPage, sortBy, keys, ...toRefs(meetingSpeechState), }; }, }); </script> 途中の悪戦苦闘の後もコメントで残してあります。 requireで、動的文字列を使えるので、これで人ごとに読み込みファイルを切り替えることができる、ということです。 以上 WordCloud は見た目のインパクトがあるので、効果的に使えると良いですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python Yahooニュースの指定したワードのみをスクレイピング

yahooニュースをいちいち確認するのが面倒と思ったので、Webスクレイピングを使ったプログラムを書いてみました。 今回は、コロナのニュースを表示させるものとなっています。 from bs4 import BeautifulSoup import requests as req import pandas as pd import re # 処理をクラスにまとめる class List: #コンストラクタにURL+ページの数を呼べるようにする def __init__(self, page): self.url = "https://news.yahoo.co.jp/topics/top-picks?page="+str(page) #以下処理メソッドをまとめる def yahoo_s(self): #ヤフーニュースの情報を取得する res = req.get(self.url) #print(res) #BeautifulSoupにヤフーニュースのページ内容を読み込ませる yahoo_html = BeautifulSoup(res.text,'html.parser') #ヤフーニュースのタイトルとURLの情報を取得する news_list = [] url_list = [] data_list = yahoo_html.find_all(href=re.compile("news.yahoo.co.jp/pickup")) #ヤフーニューズのタイトルをリストに格納 for yahoo_data in yahoo_html.select('.newsFeed_item_title'): news_list.append(yahoo_data.string) # print(news_list) return news_list #二次元内包表記にてリストに格納 all_list = [] all_coronalist = [] for i in range(10): page = List(page=i+1) plist = page.yahoo_s() all_list.append(plist) #リスト内の,コロナに関係するワードのみを格納 l_in = [s for s in plist if 'コロナ' in s or '感染' in s] all_coronalist.append(l_in) #print(allcoronalist) #二次元リストを一次元にして格納 all_corona_news = sum(all_coronalist, []) #データフレーム title_list_corona = pd.DataFrame({'Title':all_corona_news}) title_list_corona 取得したニュースをデータフレームに格納して、それをフォルダにCSVにして保存するプログラムです。 今回は、合計11件のニュースになっていますが上限は決めておらず、「#二次元内包表記にてリストに格納」のforループした範囲のすべてを取得するものになっています。 ↓が今回のデータフレームです。 LINEに取得したタイトルを送る #取得したトークン Token = 'afUaL44902wfil2Xshx4bd53sYpb47CSygCAuISpjc4' #APIのURL api_url ='https://notify-api.line.me/api/notify' #通知内容 send_title = all_corona_news #情報を辞書型にする Token_dic = {'Authorization':'Bearer' + ' ' + Token} send_dic = {'message': send_title} print(Token_dic) print(send_dic) #LINE通知を送る(200: 成功時、400: リクエストが不正、401: アクセストークンが無効: 公式より) #()内は、アクセスするWEB APIのURL、認証情報、送りたい内容 req.post(api_url, headers=Token_dic, data=send_dic) こちらとWindowsのタスクスケジューラを使い決まった時間に、LINEでニュースをチェックできるようにしました。 工夫した点 ニュースの中から特定のワードのみを取得するプログラムが書けたこと。 他のワードにも対応できる書き方が出来たところ。 妥協点 本当はURLも一緒に取得したかったのですが、指定したタイトルのURLのみを取得することが出来なかった点。 思っていたよりコードが長くなってしまった点。 LINEに送るとき、データフレームの綺麗な状態で送りたかったが、それが出来なかったので、一次元リストにして送ることにしたので、文字列が綺麗に送れなかった点。 次回実装したい点 - もっと見やすく - URL追加 - データフレームの画像化 もし、何か改善点があればコメントにてご教授していただけると幸いです。 参考 スクレイピング - https://www.youtube.com/watch?v=LgZ8Li97yoM&list=PLavQwENTsEBWylZ9HrWXejSYs4eTjvVm7&index=11
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Irisデータ処理(機械学習:前処理編)

機械学習の全体の流れ ➀データの取得 ➁データの加工(標準化、テスト用データと学習用データの分割など) ➂モデルの定義を行い、データを入力する。 ➃機械学習を行う。(順伝播と逆伝播を繰り返し、精度を上げていく) ➄機械学習の結果を評価する。問題があれば、チューニングを行う。 今回は前処理に分類される➀と➁について整理したい。 今回使用したPythonテクニック ➀One-Hot表記 ➁内包表記 ➂スライス ➃行列の初期化(onesとzeros) ➄assert(検証) ソース ・モジュールの導入 #sklearnモジュールの呼び出し from sklearn import datasets import numpy as np ・データの読み込み #データの読み込み iris_data=datasets.load_iris() #データについて、測定値(4種類)とそれに対するラベルについて取得する。 input_data=iris_data.data correct_data=iris_data.target ・データの中身の確認 print(input_data) #左側から「sepal lengh」「sepal width」「petal length」「petal width」           #sepal⇒がくpetal⇒花弁 >>> [[5.1 3.5 1.4 0.2] [4.9 3. 1.4 0.2] [4.7 3.2 1.3 0.2] [4.6 3.1 1.5 0.2] ・・・ [6.5 3. 5.2 2. ] [6.2 3.4 5.4 2.3] [5.9 3. 5.1 1.8]] print(correct_data) #0⇒setosa 1⇒vesicolor 2⇒versinica >>> [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 ・・・ 2 2 2 2 2] #データ数の確認 len(input_data) >>>150 #input_dataとcorrect_dataの個数が等しいかを確認 assert len(input_data) == len(correct_data) >>>True ・データの標準化 #データの平均値の取得 average=np.average(input_data,axis=0) #データの標準偏差の取得 std=np.std(input_data,axis=0) #データの標準化(標準値の平均値は0で、標準偏差は1になる) data=(input_data-average)/std #input_dataは行列だが、averageとstdはベクトルである。⇒ブロードキャストの使用 ・平均値、標準偏差、標準値の確認 #四つのデータごとにデータが算出されていることが確認できる。 print("平均値: {}".format(average)) print("標準偏差: {}".format(std)) print("標準値: {}".format(data)) >>> 平均値: [5.84333333 3.05733333 3.758 1.19933333] 標準偏差: [0.82530129 0.43441097 1.75940407 0.75969263] 標準値: [[-9.00681170e-01 1.01900435e+00 -1.34022653e+00 -1.31544430e+00] ・・・ [ 6.86617933e-02 -1.31979479e-01 7.62758269e-01 7.90670654e-01]] ・ラベルデータのOne-hot化 #ラベル配列の長さの取得 correct_data_length=len(correct_data) #ラベルについてOne-hotで表現する。 correct=np.zeros((correct_data_length,3)) for i in range(correct_data_length): correct[i,correct_data[i]]=1.0 #四つのデータごとに、どのラベルに属するか(属するものに対して1と表現する。それ以外は0とする) ・One-hot化したラベルデータの中身の確認 print(correct)#左から順にsetosa,vesicolor,versinica >>> [[1. 0. 0.] [1. 0. 0.] [1. 0. 0.] [1. 0. 0.] [1. 0. 0.] ・・・ [0. 0. 1.] [0. 0. 1.] [0. 0. 1.]] ・入力データについて、学習用とテスト用に分割する。 #indexリストの準備 index=np.arange(correct_data_length) #入力データ(input_data)のindexについて、奇数と偶数ごとに振り分ける #[]の中に条件を書く方法は内包表記である。 index_train=index[index%2==0] #偶数行について学習用データとする。 index_test=index[index%2!=0] #奇数行についてテスト用データとする。 #行列のスライスを用いて、実際に学習用とテスト用に分割している。 input_train=input_data[index_train,:] corrext_train=correct[index_train,:] input_test=input_data[index_test,:] corrext_test=correct[index_test,:] 補足 ・スライス #一次元配列のスライス(ただし、先頭のインデックスは0であることに注意する。) a=np.array([1,4,5,7,3,9,8]) print(a[1:4]) >>>[4 5 7] #ステップ数2でインデックス1~4の範囲でスライスする。 print(a[1:4:2]) >>> [4 7] #二次元配列のスライス b=np.array([[0,2,4], [3,5,9], [4,7,8]]) print(b[0,:])#0行のデータをすべて取得する print(b[:,0])#0列のデータをすべて取得する >>> [0 2 4] [0 3 4] 参考文献 ・はじめてのディープラーニング
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

セカント法(2変数)

セカント法(1変数) セカント法(2変数) 2変数のセカント法も作りました。 ブロイデン・フレッチャー・ゴールドファーブ・シャノン法(BFGS法)を使います。 $f(x, y)$ の最小解と最小値をセカント法で求める \begin{aligned} &\nabla f(x, y)=\left[\begin{array}{ll} f_{x}(x, y) \\ f_{y}(x, y) \end{array}\right] \\ &\left(\begin{array}{l} x_{new} \\ y_{new} \end{array}\right)=\left(\begin{array}{l} x_{k+1} \\ y_{k+1} \end{array}\right)-B_{k+1}^{-1} \nabla f(x_{k+1}, y_{k+1}) \end{aligned} BFGS公式 B_{k+1}=B_{k}-\frac{B_{k} \boldsymbol{s}_{k} \boldsymbol{s}_{k}^{T} B_{k}}{\boldsymbol{s}_{k}^{T} B_{k} \boldsymbol{s}_{k}}+\frac{\boldsymbol{y}_{k} \boldsymbol{y}_{k}^{T}}{\boldsymbol{s}_{k}^{T} \boldsymbol{y}_{k}} \boldsymbol{y}^{(k)}=\nabla f\left(\boldsymbol{x}^{(k+1)}\right)-\nabla f\left(\boldsymbol{x}^{(k)}\right) \boldsymbol{s}^{(k)}=\boldsymbol{x}^{(k+1)}-\boldsymbol{x}^{(k)} ニュートン法で $\nabla^{2} f(x_{old}, y_{old})$ というヘッセ行列を使ったところにヘッセ行列の近似行列 $B$ を用いる。 行列$B$の初期は単位行列が良いようなので、単位行列にしておきます。 1変数のセカント法の時も、初期値が2つ必要でしたが、 2変数の際も2つ必要になります。 $\boldsymbol{y}^{(k)}$ と $\boldsymbol{s}^{(k)}$ の部分を見れば分かると思います。 実装 ゴリ押しするのみです。 import matplotlib.pyplot as plt import numpy as np # 実装 def secant_method_2val(func0, func1, func2, initial_x1, initial_y1, initial_x2, initial_y2, EPS): """ func0: f(x,y) func1: f_x(x,y) func2: f_y(x,y) funcの部分はlambda関数で設定する。 func = lambda x,y : x+y+3 func(1,3) = 7 initial_x1,initial_y1 初期ベクトル1つ目 initial_x2,initial_y2 初期ベクトル2つ目 EPS:更新幅を止める基準 from BFGS Method. """ count = 0 # xの更新値の保存 x_new_list = [ ] y_new_list = [ ] diff = 10 # EPS基準で止めに行くが、止まらない値で設定しておく B_ini = np.array([[0, 1,], [1, 0]]) # Bの初期値は単位行列にしておく print("========== processing ==========") while diff >= EPS: count += 1 f_x1 = func1(initial_x1, initial_y1) f_y1 = func2(initial_x1, initial_y1) f_x2 = func1(initial_x2, initial_y2) f_y2 = func2(initial_x2, initial_y2) # 更新に必要なものを行列形式にまとめる initial1 = np.array([[initial_x1], [initial_y1]]) # 初期値1を行列格納 initial2 = np.array([[initial_x2], [initial_y2]]) # 初期値2を行列格納 nabla1 = np.array([[f_x1], [f_y1]]) # 初期値1によるnabla(x,y) nabla2 = np.array([[f_x2], [f_y2]]) # 初期値2によるnabla(x,y) # 初期値の差分の行列, s_kにあたる部分 ini_diff = initial2 - initial1 ini_diff_T = ini_diff.T # nabla(x,y)の差分の行列, y_k にあたる部分 nab_diff = nabla2 - nabla1 nab_diff_T = nab_diff.T # BFGSの更新で必要なところを求めていく # 第一項の分子 cal1 = np.dot(B_ini,ini_diff) cal2 = np.dot(cal1,ini_diff.T) cal3 = np.dot(cal2,B_ini) # 第一項の分母 cal4 = np.dot(ini_diff_T,B_ini) cal5 = np.dot(cal4,ini_diff) # 第二項の分子 cal6 = np.dot(nab_diff,nab_diff_T) # 第二項目の分母 cal7 = np.dot(ini_diff_T,nab_diff) # BFGSでBを更新 B_new = B_ini - cal3/cal5 + cal6/cal7 B_new_inv = np.linalg.inv(B_new) # 解の更新値を求める new = initial2 - np.dot(B_new_inv, nabla2) # count と 更新値 print("{}回目:x_new = {},y_new={}".format(count,new[0,0],new[1,0])) x_new_list.append(new[0,0]) # xの更新値をリストに追加 y_new_list.append(new[1,0]) # yの更新値をリストに追加 # 更新幅 diff_x = abs(initial_x2 - new[0,0]) diff_y = abs(initial_y2 - new[1,0]) # diffの大きい方でwhileを止める基準にしたい diff = max(diff_x,diff_y) # 値の更新 initial_x1 = initial_x2 initial_y1 = initial_y2 initial_x2 = new[0,0] initial_y2 = new[1,0] B_ini = B_new if count == 100: # 100回で収束しなければ diff = 0 # diffを強制的に0にして print("========== Do not CONVERSION =========") # while 文から抜ける print("========== Result ==========") print("solution:x = {}".format(new[0,0])) print("solution:y = {}".format(new[1,0])) value = func0(new[0,0],new[1,0]) # 解を代入した値 print("min_value:{}".format(value)) # グラフ描画 trial = np.arange(1, count+1, 1) # 1 ~ n回 fig = plt.figure(figsize=(15, 2.5)) ax = fig.add_subplot(1, 2, 1) x_value = np.array(x_new_list) plt.plot(trial, x_value, color="red") plt.title("x_process") plt.xlabel('trial') plt.ylabel('x_new') ax = fig.add_subplot(1, 2, 2) y_value = np.array(y_new_list) plt.plot(trial, y_value, color="blue") plt.title("y_process") plt.xlabel('trial') plt.ylabel('y_new') plt.show() 投入 $$f(x, y)=\frac{x^{4}}{2}+\frac{x^{3}}{3}-2 x^{2} y+4 x+3 y^{2}+4 y+4$$ secant_method_2val(lambda x,y: (1/2)*x**4+(1/3)*x**3-2*(x**2)*y+3*y**2+3*x-1*y+4, lambda x,y: 2*x**3+x**2-4*x*y+3, lambda x,y: 6*y-2*x**2-1, 2.9 ,1.1, 0.7 ,0.6, 0.0001) ========== processing ========== 1回目:x_new = 0.8966608356519854,y_new=1.7789380582176801 2回目:x_new = 0.46072428100335133,y_new=0.09276375276513948 3回目:x_new = 0.3214593587525236,y_new=0.03048594045777645 4回目:x_new = -2.3007446279112482,y_new=-0.5689480801613761 5回目:x_new = -0.1396017347664773,y_new=0.2929760918763705 6回目:x_new = -0.4783746153102301,y_new=0.34421672097573774 7回目:x_new = 1.873658873538518,y_new=-0.16843363867868555 8回目:x_new = -1.0413314148600892,y_new=0.1990416524047588 9回目:x_new = -1.448402437071243,y_new=0.34450415976322235 10回目:x_new = -2.789234057460034,y_new=1.4949228060550759 11回目:x_new = -2.0048205758538753,y_new=1.3471035212103724 12回目:x_new = -2.2523935466685447,y_new=1.782625309639497 13回目:x_new = -2.6623611224707515,y_new=2.4526447430979523 14回目:x_new = -2.547973663834364,y_new=2.328480053986123 15回目:x_new = -2.567727379134939,y_new=2.3629302398098053 16回目:x_new = -2.5702751956843284,y_new=2.368569196737502 17回目:x_new = -2.5702558657988095,y_new=2.3687192279024103 18回目:x_new = -2.5702482888503564,y_new=2.368725428478324 ========== Result ========== solution:x = -2.5702482888503564 solution:y = 2.368725428478324 min_value:-4.382380559808665 答え合わせ 答え合わせに使用した wolframAlpha https://www.wolframalpha.com/ 感想 ニュートン法の時は1変数の時に、分母に来ていた微分が、2変数では行列の-1乗で表すという形になっていて、直感的にもしっくりくる感じです。 セカント法の場合は同じノリ($\nabla f(x,y)$)の差分を取って逆行列で乗り切ろうとする)で実装しようとすると逆行列が見つからない問題で詰まります。 BFGS公式以外にも他にも方法はあるそうですが、うまく考えた人がいると感心してしまいますね。 BFGS法 https://ja.wikipedia.org/wiki/BFGS%E6%B3%95
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goatoolsによるGO関係可視化図の見栄えを改良してみた

前回の記事でGO解析ツール「goatools」について紹介しました。 その時のコマンドライン操作のみからのGO関係可視化図の見栄えがいまいちだったので、見栄えがよくなるように改良を試みました。 改良による見栄えの改善に関する備忘録を残しておきます。 実行環境設定 goatoolsのインストールについては前回の記事を参照 色情報を取り扱うライブラリcolourが前回から追加で必要 pip install colour Ubuntu20.04 & Python3.8.10 にて実行環境構築を実施 前回までのGO関係可視化の問題点 goatoolsのコマンドツール「go_plot.py」により下記のGO関係可視化図が前回得られた。 echo -e "#ff2200\tGO:0009409\n#ff4400\tGO:0009628\n#ff6600\tGO:0006950\n#ff8800\tGO:0009266\n#ffaa00\tGO:0009415\n#ffbb00\tGO:0001101\n#ffcc00\tGO:0050896\n#ffdd00\tGO:0009414\n#ffee00\tGO:0010035\n#ffff00\tGO:0006970" \ > go_enrichment_color_list.txt go_plot.py --go_file=go_enrichment_color_list.txt --outfile=plot_color_go_enrichment_BP.png GO関係可視化図を見ると見栄えに関して以下の点が気になり、何とかしたいなと感じました。 GO機能説明が横に長いため、図全体が横長で見づらい 「L1 D1 d1979」のようなGOの階層・深さ等の記載が邪魔に感じる pvalueを元に色の強弱をつける想定で可視化したが、pvalueの記載が図中にないので分かりづらい。 GO関係可視化の改良結果 下記の改良をしたPythonスクリプトを作成しました(スクリプトは一番最後に記載)。 GO機能説明が20文字以上の場合に改行 GOの階層・深さ等の出力をしないように設定 pvalueをGO機能説明とともに出力する pvalue値の大小関係を表現する段階色を自動選択する 見栄えを改良したコード実行 python color_go_plot.py color_go_plot.png 出力結果図を見ると、問題点が改良され、見栄えが良くなった。 その他例:可視化するGO数を増やして段階色の指定を変更 (ソース内での段階色指定について、convert_hexcolor_gradient()関数の引数にてfrom_color="lightblue", to_color="green"に変更) 実行ソース とりあえずのサンプルコードなので、入力するGOとPvalueは現状ではコード内に埋め込んでいる。 外部ファイルからGOと対応するPvalueを入力できるようにすれば、より汎用的に利用できるようになる。 このソースをベースにし、目的に応じてコードを書き換えれば、ある程度のプロット作成に対応できそう。 color_go_plot.py import math import sys from typing import Dict, List, Optional import numpy as np from colour import Color from goatools.godag_obosm import OboToGoDagSmall from goatools.godag_plot import GODagPltVars, GODagSmallPlot from goatools.obo_parser import GODag def main(plot_outfile: str): """Plot Color GO DAG(Directed Acyclic Graph) Args: plot_outfile (str): Plot output file path """ # GOenrichment result example goid2pvalue = { "GO:0009409": 4.42507619016377e-16, "GO:0009628": 1.70399392910078e-14, "GO:0006950": 1.70399392910078e-14, "GO:0009266": 2.58192361031249e-14, "GO:0009415": 6.399674360673e-14, "GO:0001101": 6.44890640551705e-14, "GO:0050896": 3.4574338076728e-13, "GO:0009414": 3.81276641723126e-13, "GO:0010035": 4.04311940400782e-11, "GO:0006970": 1.48573634972923e-07, # "GO:0009737": 9.21442857296839e-07, # "GO:0097305": 9.35271973837119e-07, # "GO:0042221": 9.35271973837119e-07, # "GO:1901700": 3.44241932707208e-06, # "GO:0009269": 7.13392304953131e-06, # "GO:0009631": 1.19992021145805e-05, # "GO:0033993": 2.06259190472414e-05, # "GO:0009725": 0.000458054576231, # "GO:0009719": 0.000458054576231, } # Convert pvalue to common logarithm(log10) of the absolute value pvalue_abs_log10_list = [abs(np.log10(v)) for v in goid2pvalue.values()] # Get hexcolor from converted pvalue pvalue_hexcolor_list = convert_hexcolor_gradient( pvalue_abs_log10_list, from_color="yellow", to_color="red" ) goid2color = { go: hexcolor for go, hexcolor in zip(goid2pvalue.keys(), pvalue_hexcolor_list) } # Plot color go dag color_go_plot(plot_outfile, goid2color, goid2pvalue) def convert_hexcolor_gradient( value_list: List[float], step_num: int = 20, default_min: Optional[float] = 0, default_max: Optional[float] = 10, from_color: str = "yellow", to_color: str = "red", ) -> List[str]: """Convert to gradient hexcolor (e.g. "#ffff00") list based on the size of the value Args: value_list (List[float]): List of float values step_num (int, optional): Number of gradient steps. Defaults to 10. default_min (float, optional): Default minimum value. Defaults to 0.05. default_max (float, optional): Default maximum value. Defaults to 10. from_color (str, optional): Start color for the gradient. Defaults to "yellow". to_color (str, optional): End color of the gradient. Defaults to "red". Returns: List[str]: List of gradient hexcolor """ # Create gradient hexcolor list color_gradient_list = list(Color(from_color).range_to(Color(to_color), step_num)) hexcolor_gradient_list = [color.get_hex_l() for color in color_gradient_list] # Define value range for gradient hexcolor min_value, max_value = min(value_list), max(value_list) if default_min: min_value = min(min_value, default_min) if default_max: max_value = max(max_value, default_max) value_range_list = list(np.linspace(min_value, max_value, step_num + 1))[1:] # Get gradient hexcolor corresponding to input value convert_hexcolor_gradient_list = [] for value in value_list: for idx, value_range in enumerate(value_range_list): if value <= value_range: convert_hexcolor_gradient_list.append(hexcolor_gradient_list[idx]) break return convert_hexcolor_gradient_list def color_go_plot( plot_outfile: str, goid2color: Dict[str, str], goid2pvalue: Dict[str, float] = {}, obo_file: str = "go-basic.obo", ) -> None: """Plot GO DAG using self-defined GO color Args: plot_outfile (str): Output plot file path goid2color (Dict[str, str]): go id and hexcolor dict goid2pvalue (Dict[str, float], optional): go id and pvalue dict. Defaults to {}. obo_file (str, optional): OBO file path. Defaults to "go-basic.obo". """ # Get plot target GO DAG obodag = GODag(obo_file) godagsmall = OboToGoDagSmall(goids=goid2color.keys(), obodag=obodag).godag # Wrapping GO description line at appropriate location for v in godagsmall.go2obj.values(): if len(v.name) < 20: continue split_word = v.name.split(" ") split_cnt = math.ceil(len(split_word) / 2) - 1 line_wrap_name = "" for cnt, word in enumerate(split_word): newline_or_space = "\n" if cnt == split_cnt else " " line_wrap_name += word + newline_or_space v.name = line_wrap_name # Add pvalue to the end of GO description for v in godagsmall.go2obj.values(): if v.id in goid2pvalue.keys(): v.name += f"\n{goid2pvalue[v.id]:.2e}" # Suppress useless header plot (e.g. L2 D2) godag_plg_vars = GODagPltVars() godag_plg_vars.fmthdr = "{GO}" # Create plot obj & add plot color godagplot = GODagSmallPlot(godagsmall, abodag=obodag, GODagPltVars=godag_plg_vars) godagplot.goid2color = goid2color # Plot color go dag godagplot.plt(plot_outfile, "pydot") if __name__ == "__main__": args = sys.argv main(args[1]) 参考 goatoolsによるGO解析方法まとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【勉強記録】正規表現の分解

目次 趣旨 マッチの確認 分解してみる 振り返り 参考サイト 趣旨 正規表現について学習しました。 今回はこちらで公開されている正規表現のサンプルをいくつかお借りして、それを分解することでどのような表現で実現しているのか確認してみたいと思います。 マッチの確認 Pythonの標準モジュールである、re(正規表現操作)を利用して下記の通り確認していきます。 (手元ではこのコードで試しています。) regular_expression.py import re numbers = "123" # 「数字を1文字以上繰り返す」ことの確認 confirm = re.search('[0-9]+', numbers) # => <re.Match object; span=(0, 3), match='123'> 分解してみる 本題です。 名前 ^[ぁ-んァ-ヶー一-龠]+$ 使われている表現 説明 ^ 先頭文字にマッチ [] 文字の集合を表す ぁ-ん ひらがな ァ-ヶ カタカナ 一-龠 漢字(Unicode JIS内字) ー 伸ばし棒 + 直前の表現を1回以上繰り返す $ 末尾にマッチ 言い換えると、 先頭から末尾までが、一文字以上のひらがな・カタカナ・漢字・ー(伸ばし棒)のいずれかで構成されている ちなみに、ひらがなの表現が小文字の「ぁ」から始まる理由はUnicode対応 文字コード表の並びが、ぁ、あ、ぃ、い…という並びになっているからです。他も同様です。 電話番号 ^0\d{1,4}-\d{1,4}-\d{3,4}$ 使われている表現 説明 ^ 先頭文字にマッチ \d [0-9]と等価 {1,4} 直前の表現を1~4回繰り返す $ 末尾にマッチ 先頭が0+1~4文字の数字、1~4文字の数字、末尾が3~4文字の数字で構成され、-(ハイフン)で繋がっている メールアドレス ^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$ 使われている表現 説明 ^ 先頭文字にマッチ a-zA-Z0-9 アルファベットの大文字/小文字/数字 + 直前の表現を1回以上繰り返す ._- 記号 * 直前の表現を0回以上繰り返す $ 末尾にマッチ 先頭文字と@の次の一文字はアルファベットか数字、加えて@以前はアルファベットか数字か定められた記号を0回以上、@以降は1回以上使用して構成されている つまり最低限あればいいのは@前に1文字、@以降に2文字ということになります。1@23でマッチします。 尚、Pythonではバックスラッシュがエスケープ文字なので、このままでは"\"は認識されませんでした。 "\\"にしても変わらず、解決に至らなかったため検証としては不完全なものになってしまいました…。 振り返り 以前架空のECサイトを作成した際のバリデーションを設定するために今回取り上げた正規表現を使わせて頂いておりました。 改めて調べてみるとどういう仕組みで実現していたのかがよくわかりました。また、せっかくなら条件をもう少し厳しくしたいと思う点もあり、勉強した点を踏まえて変更してみたいと思います。 日本語として正確に表現するのはかなり冗長になるのである程度省略したつもりですが…ご覧の通りです。記号の強みですね。 参考サイト https://designsupply-web.com/media/knowledgeside/1640/ https://docs.python.org/ja/3/library/re.html#module-re https://www.ogiso.net/wiki/index.php?%BC%F8%B6%C8%BB%F1%CE%C1/%C0%B5%B5%AC%C9%BD%B8%BD https://ameblo.jp/tondemonight/entry-1011710
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TTC003 公式解説

これは何 この記事は、TTC003の各問題の考え方と回答、想定されるAC解の実行ステップ数をまとめたものです。 いくつかの回答例が示されていますが、この他にも様々な回答が考えられます。 A - 桜並木(100) 想定されるAC解の実行ステップ数:$\Theta(1),\Theta(\frac{N}{k})$1など いわゆる植木算で解く問題です。 $N$ が $k$ で割り切れるため、端の部分の例外を考えなくて良いです。 整数での割り算はint(N/k)やN//kが考えられますが、精度を考えると//を使う方が望ましいでしょう。 道路の両側に木があるため、2をかけることを忘れないようにしましょう。 整数を文字列として扱う場合はstr()関数やf-stringなどを利用できます。 実装例1:strを使ったもの N=1 k=1 print(str(2 * (N // k + 1)) + '本') また、植木算で足す端の2本を別で足すという方法もあります。 実装例2:f-stringを使ったもの N=1 k=1 print(f'{2*N//k+2}本') B - dを言ったらだめ!(200) 想定されるAC解の実行ステップ数: $\Theta(1),\Theta(\frac{d}{k})$など 正式な名前は知らないけどみんな遊び方を知ってる遊びの代表格(要出典)に関する問題です。 最終的に$d-1$を言えばいいので、$d-1$から$k+1$ずつ減らしていくと、言うべき数が全て求まります。 例えば一般的な$d=30,k=3$のルールですと、$29$から$4$ずつ減らしていくことになるので、 必勝側が言うべき数は$29,25,21,17,13,9,5,1$の順で求まります。 この中で最小の$1$を先攻が言えるので先攻が必勝です。 先攻は初手で$k$までしか言えないため、言うべき最小の数が$k+1$になるときだけ後攻が必勝です。 ここまでのことから、$d-1$から$k+1$ずつ減らしていって$k+1$以下になった時、$k+1$かどうかで場合分けを行うことで解けます。 実装例1:while文を利用した求め方 k = 3 d = 6 i=d-1 while i>k+1: i-=k+1 if i==k+1: print('後攻') else: print('先攻') また、 $k+1$ から更に $k+1$ を引くと$0$になることから、 $d-1$ が $k+1$ で割り切れるかどうかで場合分けを行っても良いです。 200点問題なので、どちらで解いても間に合うような制約にしました。 実装例2:d-1がk+1で割り切れるかで場合分けする例 k = 3 d = 6 if (d-1)%(k+1)==0: print('後攻') else: print('先攻') 他にも、$d$を$k+1$で割ったあまりが$1$かどうかで判定すると短く書けます。 また、k+1を-~kと書くことで括弧がいらなくなり短くかけます。 また、k=d=5などと書くと$k$に$d$が代入されWA2となるので注意してください。 実装例3:短縮した例 k=3 d=6 print('先後'[d%-~k==1]+'攻') C - 多項式の値(300) 想定されるAC解の実行ステップ数: $\Theta(N),\Theta(N\log N)$など ここから、(程度は難易度によりますが)計算を効率化していかないといけない問題になります。 **を用いて累乗してからあまりを取るとTLE3してしまいますが、 pow()関数は第3引数を指定することであまりの計算をしてくれます。累乗しながらあまりを取るため高速です。 N-i-1の部分で詰まるかもしれませんが、入力例などを見れば大丈夫だと思います。 実装例1:pow関数を用いたもの N=3 A=[1,2,3] x=2 ans=0 for i in range(N): ans+=A[i]*pow(x,N-i-1,10**9+7) print(ans%(10**9+7)) また、ホーナー法という、$x$で順にくくりだすことによるより高速なアルゴリズムもあります。 入力例でいうと、$x^2+2x+3=(((0)x+1)x+2)x+3$とすることで、$0$に$x$をかけて係数を足すという操作だけで求まります。 実装例2:ホーナー法を用いたもの A=[] x=0 N=0 a=0 for i in A:a=a*x+i print(a%(10**9+7)) 300点問題なので、どちらの方式でも解けるようにしました。 D - 再帰っぽい関数(400) 想定されるAC解の実行ステップ数: $\Theta(n)$など 定義のとおりに再帰的に実装してしまうと計算量が多すぎてTLEとなります。 この関数が組み合わせCを用いて$_n C_r$と表せると気づけるかにかかっています。 気づけたら$_n C_r$の定義から$\dfrac{n!}{r!(n-r)!}$をそのまま計算するだけです。 階乗はmath.factorial()を使います。「メモ化」という、$0$からある数$a$までの階乗を事前に記録しておく方法がありますが、 その方法を使わなくても間に合う制約になっています。 実装例1:定義どおりに実装した n=0 r=0 from math import factorial as f print((f(n)//f(r)//f(n-r))%(10**9+7)) 9/7(火)にTOYPROでのPythonのバージョンが3.9にアップデートされたため、math.comb()が使えるようになりました。 $math.comb(n,r)={_n C_r}$なので代入すればACできます。$10^9+7$で割ったあまりを答えることに注意してください。 実装例2:math.comb()を用いたもの n=0 r=0 import math print(math.comb(n,r)%(10**9+7)) E - 割り算の筆算(500) 想定されるAC解の実行ステップ数: $\Theta(m)$など 愚直に筆算のあまりの部分をリストに記録し、(余り) in (リスト)とするとTLEしてしまいます。 また、setのほうが速いですが、それでも探索に時間がかかるためTLEします。 $\Theta(m \log m)$では間に合わない、少々きつめの制約となっています。 詳しい証明は省きますが、分母と分子で約分した後分母から2,5の素因数を無くした時1なら有限小数、それ以外は無限小数になります。 この時分数はすでに約分されているので、分子の値によらずループの桁数は同じです。なので、分子を1として計算することで、 割ったあまり=1とすることで高速に判定できます。 2,5の素因数がないことで、循環節が必ず小数第1位から始まるため、循環したかの判定を$O(1)$で行うことができます。 実装例1:解説通りの実装 n=1 m=49 from math import gcd m//=gcd(n,m)# 分母を約分、分子は1としていいので処理しない while m%2<1:m//=2#2で割れるだけ割る while m%5<1:m//=5#5で割れるだけ割る if m==1:print(0)#2,5以外の素因数を持たないなら else: n=1#分子を1として考える i=0#ループ数=循環の桁数をカウント while 1:#最低1回は実行するのでここに条件を入れてはならない n=10*n%m#割り算の筆算を再現。0をおろしてmの倍数を引いたあまり i+=1#カウントを1増やす if n==1:#ループしたなら print(i)#ループまでに要した桁数を出力 exit()#ループから脱出するためにexit()関数を使った。breakも可 F - あみだくじの繰り返し(600) 想定されるAC解の実行ステップ数: $\Theta(N\log N),\Theta(N^2)$など 出力すべき数は最大で$10^{35}$を超えるため、全探索していたら間に合いません。 どんなあみだくじも有限回繰り返せば一切入れ替わらないあみだくじと同じものになるという問題です。 難しく書こうと思えば大学数学まで必要ですが、TOYPROは小学5年生から始めるサイトなので大まかに書きます。 まずはあみだくじ1個分でどのように入れ替わるのかを調べます。 すると、以下のように周期的に値を入れ替える部分が存在します。 例えば、(左から)1番目が2番目に、2番目が3番目に、3番目が1番目に、4番目が5番目に、5番目が4番目に入れ替わるようなあみだくじがあったとしましょう。これは、1,2,3番目と4,5番目がそれぞれ順番に入れ替わっていると捉えることが出来ます。 すると、1,2,3番目の部分は3回の周期で元通りになり、4,5番目の部分は2回の周期で元通りになることが想像できます(実際そうなります)。 3の倍数でも2の倍数でもある「最小の」数なので、これらの周期の最小公倍数を答えればいいとわかります。 ループがある要素を全て高速に見つけ出す事ができれば、Eまで解けている方なら問題ないかなと思います。 ある一つの要素をあみだくじを繰り返し通過させてループを検知し、そのループ中に通った全ての箇所を探索箇所から削除すると間に合います。 実装例1:解説通りの実装 N=10#あみだくじの縦線の本数 A=[0,1,2,3,4,5,6,7,8]#入れ替える場所の位置と順番 B=[*range(N)]#各縦線の番号を記録(初期値) for i in A: B[i],B[i+1]=B[i+1],B[i]#実際に入れ替えて入れ替わり方を調べる b={*range(N)}#探索すべき開始地点 ans=1#全く入れ替わらないなら1回でOKなので、初期値は1 from math import lcm#最小公倍数を使えるように from random import sample#次に調べるべき数をランダムに決定するために while b!=set():#調べるべき要素がまだある間 d=sample(b,1)[0]#sampleの出力はlistなので[0]で最初の要素を取り出す e=d#あみだくじ通過前の数を記録 f=0#ループの数 while 1:#ループしたらbreakでループを抜けるのでここに条件を書かない b-={d}#すでに探索済みの地点の削除 d=B[d]#次の位置を求める f+=1#ループの数 if e==d:#あみだくじ通過前と一致したら(ループしたら) ans=lcm(ans,f)#ansとfの最小公倍数を求める break#次のノードへ print(ans) 他にも、Union Findというアルゴリズムでグループ分けをすることもできます。 下の実装例は後処理に改善の余地ありですが、あくまでも例なので悪しからず。 実装例2 Union Findを用いたもの N=10 A=[0,1,2,3,4,5,6,7,8] B=[*range(N)] for i in A: B[i],B[i+1]=B[i+1],B[i]#この行までは実装例1と同じ d=list(range(N)) e=[0]*N def find(n): global d if n!=d[n]: d[n]=find(d[n]) return d[n] def join(a,b): a,b=find(a),find(b) global d,e if e[a]<e[b]: d[a]=d[b] else: d[b]=d[a] e[a]+=(e[a]==e[b]) for i,j in enumerate(B): join(i,j)#ループの入力と出力同士をつなぎます ans=1 from math import lcm for i in set(d):#グループの数だけ f=d.count(i)#各グループの要素数との最大公約数を取る ans=lcm(ans,f) print(ans) G - リスト圧縮!(1000) 想定されるAC解の実行ステップ数: $\Theta(\log(int(S)))$ リスト全列挙では最大の入力($S>10^{480}$)に間に合いません。 自然数と有限自然数列は見た感じ有限自然数列の方が圧倒的に多そうですが、実は同じ数だけあるという問題です。 実はこの問題では自然数とリストが実は1対1に結び付けられています。 アルゴリズムの流れは、 S→int(S)→(リストの長さと同じ長さの中でのRank)→目的のリスト と求めるのが自然でしょう。 最後の変形はこの問題と同じように解けば良いです。 1000点問題はトイプロの順位に大きく影響するため、ここでは実装例をあえて載せません。 つよい人に解いてもらうのを楽しみにしています。 おわりに 今回の問題は、「数学の新たな学び」を意識して、教科書通りだけではちょっと難しい問題を作ったつもりです。 解いていて新たな発見があったり、解説を見て「そうだったのか!」といった学びがあったら良いな―と思います。 これらの問題の方針を2秒で理解できるレベルのつよつよさんには物足りなかったかもしれませんが、 すでにある程度学んでいないと解けないはずですので、すでに学びを得ているという意味では問題ないと思います。 Θ(f(x))はちょうどf(x)と同じくらい、の意味 ↩ Wrong Answer - 「不正解」の意 ↩ Time Limit Exceeded - 「時間切れ」の意(Wikipediaより引用) ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pandasのapplyの使い方!

執筆の経緯 pandasでapplyを使うときにやり方忘れがちなので、個人的メモとしてまとめておきます。 無名関数の場合 df['dessert'] = df['dessert'].apply(lambda x : x.lower()) apply関数はreplace=Trueを使えないので、applyしたものは代入して上書きが必要です。 ここも忘れがちなので注意。 宣言された関数を利用する場合 def make_dessert_name_lower(x): return x.lower() df['dessert'] = df['dessert'].apply(make_dessert_name_lower) ()内は関数名のみの指定で通常の関数宣言のようにmake_dessert_name_lower(x)としなくても大丈夫です。 おまけ applyしないでseriesに一括処理を加えたい場合 desserts = [] for dessert in df['desert']: lower_dessert = dessert.lower() desserts.append(lower_dessert) df['desert'] = dessert 必要になるケースがあるか分かりませんがよく見かける書き方です。 ここまで、お付き合いいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リストから名前指定remove()したときに同じ名前の要素が複数あったらどうなるか

気になったこと python3のリスト操作で、remove()関数を使って指定した要素を削除することがありますが、リストの中に同じ要素があった場合にどのような消え方をするのかふと心配になったので確認しました。 確認結果 「apple」の文字列から「p」をremove()しましたが、消えた「p」は1個だけでした。安心しました。 >>> test_list = list("apple") >>> print(test_list) ['a', 'p', 'p', 'l', 'e'] >>> test_list.remove("p") >>> print(test_list) ['a', 'p', 'l', 'e']
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】P294: Sum of digits - experience #23のメモ(その1)

23の倍数で各桁の和が23の数を1112まで数える 再帰関数+lru_cache P294: Sum of digits - experience #23 は表題のように簡単そうな問題ですが、数がとんでもなく大きいのでギブアップでした。 まず普通に再帰関数で書くとこんな感じですが時間がかかって問題外。 def S(nd,m,ds): #桁 10**nd, m: 23で割った余り, ds: 桁の和 if nd == 0: return 1 if (m % 23 == 0) and ds == 23 else 0 ret = 0 for d in range(min(23-ds+1,10)): ret += S(nd-1,(m*10+d)%23,ds+d) return ret ND = 42 print((f"ND = {ND}, answer = {S(ND,0,0)}) そこでお約束のlru_cacheを設定すると劇的に改善して114までは何とか1分以内で終了。でも先は遠い、、、 from functools import lru_cache @lru_cache(maxsize=None) 動的計画法(1条件) この手の問題は動的計画法かなと思いましたが、あまり得意ではないので、まず条件の1つの各桁の和が23のみを書きました。 import numpy as np n, D = 9, 23 dp = np.array([0] * (n+1)*(D+1)).reshape(n+1,D+1) dp[0, 0] = 1 for i in range(n): for j in range(D+1): for k in range(min(D-j+1,10)): dp[i+1,j+k] += dp[i,j] print(dp[n,D]) 動的計画法(2条件) この問題では以下の2つの条件があるので、 各桁の和が23 23で割り切れる DP tableを(23+1 x 23+1)の2次元mapにして、X軸で23で割った余り、Y軸で桁の数の合計を、各桁ごとにカウントするようにしました。計算には一つ前の桁の値しか使わないので、2つのmapを交互に使うようにしました。(使った後で0でクリアしてます) でもこれも1分で解けるのはk=114までで、今回はちょっとお手上げでした。 import numpy as np def clearMap(map): for i in range(len(map[0])): for j in range(len(map)): map[i,j] = 0 # clear next table for reuse def dpMap(n, D, M): BF, D1, D1 = 2, D+1, D+1 # 桁、桁の和、余り、 dp = np.array([0] * BF*D1*D1).reshape(BF, D1, D1) dp[0, 0, 0] = 1 for i in range(n): # 桁 clearMap(dp[(i+1)%2]) # 次のmapを0でクリア for d in range(D1): # 桁の和 for m in range(D1): # 余り for k in range(min(D-d+1,10)): # i+1番目の桁の数 (d+k<=D) dp[(i+1)%2,d+k,(m*10+k)%D] += dp[i%2,d,m] dp[(i+1)%2,d+k,(m*10+k)%D] %= M return dp[(i+1)%2,D,0] n = 42 ans = dpMap(n, 23, 10**9) print(f"n = {n}, ans = {ans}")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Flask】仮想環境構築と「Hello World!」の表示(5分)

環境 Microsoft Windows 10.0.19042.1165 Python 3.9.4 Flask 1.1.2 ディレクトリ構成 flask_test/ ├─ venv/ └─ app.py 手順 Pythonはインストール済みとする 仮想環境用ディレクトリflask_testを作成(mkdir [フォルダ名]) mkdir flask_test flask_testに移動(cd [フォルダ名]) cd flask_test 仮想環境を作成(python -m venv ./[フォルダ名]) python -m venv ./venv 仮想環境を有効化([フォルダ名]¥Scripts¥activate) venv¥Scripts¥activate --> 先頭に(env)が付く 無効化するときは「deactivate」と入力 仮想環境の中にFlaskをインストール pip install Flask flask_testフォルダの中にapp.pyを作成 app.py from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Hello World!" if __name__ == "__main__": app.run(debug=True) 実行する Flask run http://localhost:5000/ にアクセスすると"Hello World!"と表示される ちなみに 以下のように入力するとrequirements.txtに現在の環境(設定)を書き出すことができる pip freeze > requirements.txt
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Flask】5分で仮想環境構築と「Hello World!」の表示

環境 Microsoft Windows 10.0.19042.1165 Python 3.9.4 Flask 1.1.2 ディレクトリ構成 flask_test/ ├─ venv/ └─ app.py 手順 Pythonはインストール済みとする 仮想環境用ディレクトリflask_testを作成(mkdir [フォルダ名]) mkdir flask_test flask_testに移動(cd [フォルダ名]) cd flask_test 仮想環境を作成(python -m venv ./[フォルダ名]) python -m venv ./venv 仮想環境を有効化([フォルダ名]¥Scripts¥activate) venv¥Scripts¥activate --> 先頭に(env)が付く 無効化するときは「deactivate」と入力 仮想環境の中にFlaskをインストール pip install Flask flask_testフォルダの中にapp.pyを作成 app.py from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Hello World!" if __name__ == "__main__": app.run(debug=True) 実行する Flask run http://localhost:5000/ にアクセスすると"Hello World!"と表示される ちなみに 以下のように入力するとrequirements.txtに現在の環境(設定)を書き出すことができる pip freeze > requirements.txt
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

全株式の20年分の値動きを取得する

はじめに 株式運用を始めるにあたって長期的な値動きを把握したいと思ったので。 それだけならYahoo!株式とか見てりゃいいんですけど、 データの加工なんかを想定するとAPIで色々操作したほうが良さそうだよね ということでYahoo Finance API を使った上場株式の値動きを一気に取得できるプログラムを作りました。 目次 APIから元データを取得する 元データから日付と終値のみを取得する Github APIから元データを取得する main.py def get_symbol_data(share_code): my_share = share.Share(share_code) symbol_data = None try: symbol_data = my_share.get_historical(share.PERIOD_TYPE_YEAR, 20, share.FREQUENCY_TYPE_DAY, 1) except YahooFinanceError as e: print(e.message) sys.exit(1) return symbol_data share_codeには "1570.T"のような文字列が入ります。 main.py symbol_data=get_symbol_data("1570.T") print(symbol_data.keys()) >>dict_keys(['timestamp', 'open', 'high', 'low', 'close', 'volume']) 各Keyには1日毎に日時や株価を取得した配列が入っています。 main.py prices = symbol_data["close"] print(prices) >>[100,101,99...] 元データから日付と終値のみを取得する main.py def return_dates_prices(symbol_data): dates=symbol_data["timestamp"] formated_dates = [datetime.utcfromtimestamp(int(dates[i]/1000)) for i in range(len(dates))] formated_dates_only_dates=[formated_dates[i].strftime("%Y-%m-%d") for i in range(len(formated_dates))] prices = symbol_data["close"] return formated_dates_only_dates,prices formated_dates,prices=return_dates_prices(symbol_data) >>formated_dates=["2000-01-01","2000-01-02","2000-01-03"...] >>prices=[100,101,99...] みたいな感じで 日付と終値を約20年間くらい取得することが出来ます。 GitHub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[環境構築] Pythonのバージョン切り替えができない

はじめに pythonを使ったアプリ開発に挑戦しようとしたところpythonのインストールの時点でつまづいたので記事にしておきます。 つまった時の状況 使用しているのはmacOSのCatalina10.15.7 シェルはzsh homebrewを用いてpyenvをインストール(pyenv 2.0.6) ターミナル brew install pyenv 下記のコマンドを順に実行(.zshrcにpyenv設定を書き込んでいくコマンド) ターミナル echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc echo 'eval "$(pyenv init -)"' >> ~/.zshrc source ~/.zshrc pyenvを用いてPythonをインルトール(Python 3.9.7) ターミナル pyenv install 3.9.7 標準で搭載されているPythonからインストールしたPythonに切り替え ターミナル pyenv global 3.9.7 切り替わっているか確認 ターミナル python --version Python 2.7.16 ファッ!? 解決した方法 .zshrcの記述を疑う 公式のgitのreadmeと他の方の記事を参考にこの部分を修正↓ .zshrc(修正前) # ~省略~ eval "$(pyenv init -)" .zshrc(修正後) # ~省略~ eval "$(pyenv init --path)" あとは、念のため~/.python-versionが生成されたりするとよろしくないらしいので、削除するコマンドを実行しておきました。いろいろ試していると知らず知らずに生成されていたりするらしいです。 ターミナル rm ~/.python-version .zshrcを読み込ませます。 ターミナル source ~/.zshrc 再度切り替えのコマンド ターミナル pyenv global 3.9.7 確認 ターミナル python --version Python 3.9.7 わーーい!! 最後に 公式のドキュメントやgitを確認するのはほんと大切だなと感じました。 参考: https://github.com/pyenv/pyenv#installation https://qiita.com/myy/items/a526bdb43982cf82f96a
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ansible×pandas】Excelからデータの入力/出力がしたい

はじめに バージョン Python: 3.6.8 pandas: 1.1.5 openpyxl: 3.0.8 xlrd: 1.2.0 概要 Ansibleで自動化対応をしていると、エクセルからデータを読み込めないか?と良く聞かれます。 Ansibleの標準モジュールにはエクセル用のが無いのでpythonで作ってみます。ただし、pythonの部分を 作りこみすぎると、Ansibleのシンプルさが損なわれてしまうので、シンプルなコードを書きます。 今回はエクセルファイル(sample.xlsx)の以下、2つのシートからデータを抽出します。 このデータから、ageが30以上のデータを新たなエクセルに出力するコードを作成します。 Sheet1 Sheet2 1. サンプルコード 1-1. 実行用playbook playbookと処理の流れは以下のようになっています。 1. Excelファイルの読み込み 変数in_excelに記載されたエクセルの内容を読み込む 2. 条件に合ったデータの抽出 ageが30以上のデータを抽出 3. エクセルへの出力 変数out_excelに記載されたエクセルに結果を出力する read_excel.yml --- - hosts: localhost connection: local gather_facts: false tasks: - name: "input from excel" # ポイント(1-1) script: >- ./script/input_excel.py {{ in_excel }} vars: in_excel: "/home/centos/ansible/data/sample.xlsx" register: input_result args: executable: python3 - name: "fetch age >= 30" # ポイント(1-2) set_fact: fetch_result: "{{ input_result_json | json_query(query_string) }}" vars: input_result_json: "{{ input_result.stdout }}" query_string: "[?age >= `30`]" - name: "output to excel" # ポイント(1-3) script: >- ./script/output_excel.py {{ out_excel }} "{{ fetch_result }}" register: output_result vars: out_excel: "/home/centos/ansible/output/result.xlsx" args: executable: python3 when: - fetch_result != [] # ポイント(1-4) ポイント 1-1. エクセルからデータを抽出する scriptモジュールでは以下のように引数の指定が可能です 引数はコード内でsysモジュールを使って取得します(詳細は後述) - name: "run script" script: sample.py 引数1 引数2 args: executable: python3 1-2. エクセルからデータを抽出する jmespathの使い方は過去の記事を参考にしてください 【Ansible】json_queryでcontain/starts_with/ends_with/and/orを使ってみた 1-3. エクセルへの出力 scriptの記述をする部分で、"{{ fetch_result }}"のようにダブルクォーテーションで囲っています。 このようにしないと、fetch_result内のスペースが引数と引数の境界だと認識されてしまうので注意してください。 1-4. 出力結果が存在しない場合への対処 jmespathの結果が空であった場合は、処理をskipさせます 1-2. Excelデータ出力用script 以下が、Excelデータ出力用scriptです。処理の内容はコード内に記載してありますので、 そちらをご参照ください。また、記事の最後にdocumentのリンクを貼っておきます。 input_excel.py import pandas as pd import sys # 引数を取得(引数の順番は1からスタート) file_path = sys.argv[1] # エクセルからデータを取得する(sheet_name=Noneで全てのシート) df = pd.read_excel(file_path, sheet_name=None) for sh_name in df: # 新たな列(sheet)を作成し、シート名を記載 df[sh_name]["sheet"] = sh_name # シートごとのDataFrameを結合する(ignore_index=Trueで通し番号を削除) df_all = pd.concat(df, ignore_index=True) # DataFrameをdictに変換して出力する print(df_all.to_dict(orient="records")) 1-3. エクセルへの出力用script 以下が、エクセルへの出力用scriptです。処理の内容はコード内に記載してありますので、 そちらをご参照ください。また、記事の最後にdocumentのリンクを貼っておきます。 outpu_excel.py import pandas as pd import sys import ast # 引数を取得(引数の順番は1からスタート) file_path = sys.argv[1] # ast.literal_eval(文字列)で文字列型 -> 辞書型へ変換 name_list = ast.literal_eval(sys.argv[2]) # name_listをもとに、DataFrameを作成 df = pd.DataFrame(data=name_list) # 列sheetから出力するシート名の一覧を取得(drop_duplicatesで列の項目の重複を排除) sh_list = df["sheet"].drop_duplicates() # 出力するエクセルファイルのパスを指定 with pd.ExcelWriter(file_path) as writer: # シートごとにデータの出力を実施 for sh_name in sh_list: # 出力するシート名に対応するデータを指定(dropで列sheetを削除) output_data = df[df.sheet == sh_name].drop("sheet", axis=1) # 出力するシート名に対応するデータを出力(index=Falseで行番号を削除) output_data.to_excel(writer, sheet_name=sh_name, index=False) 2. 実行結果  想定通りの結果を得ることが出来ました。 Sheet1 Sheet2 まとめ  pandasは内容が難しいですが、自動で表を作成してくれてデータ処理が楽になるので覚えておきましょう。 参考記事 script - Ansible Documentation pandas.read_excel — pandas 1.3.2 documentation pandas.concat — pandas 1.3.2 documentation pandas.DataFrame.to_dict — pandas 1.3.2 documentation pandas.DataFrame.drop_duplicates pandas.DataFrame.drop — pandas 1.3.2 documentation pandas.DataFrame.to_excel — pandas 1.3.2 documentation JMESPATH - Home
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonのImageFontで使うフォントファイルをコード内に記述する

Qiitaの初投稿記事です 調べても出てこず、結構苦戦したので記事にしてみました 概要 pyinstallerというpythonをexeに変換するソフトを使用する際にフォントのパス設定で困ったので、インラインで書けないかと試行錯誤した話 元となるコード font.ttfというフォントを読み込んでfont_testと書いた画像を作るプログラム image.py from PIL import Image, ImageDraw, ImageFont from io import BytesIO im = Image.new('RGB', (900, 600), "#ffffff") draw = ImageDraw.Draw(im) font = ImageFont.truetype("./font.ttf",200) draw.text((50,50), "font_test", fill="#000000", font=font) im.save("./test.png") 使用するライブラリ PillowとBytesIOを使用します インストールされていない場合はインストールしてください 方針 準備 バイナリのフォントファイルをよみこむ メインのコード 読み込んだデータをPythonに記述する BytesIOを使ってメモリ上にフォントファイルを保存する ImageFontでメモリ上のフォント 完成したコード 準備 read_font.py from io import BytesIO path = "./font.ttf" # ここに読み込むフォントファイルのパス with open(path,"rb") as f: s = f.read() # フォントの読み込み with BytesIO() as bs: bs.write(s) content = bs.getvalue() with open("./font.txt","a") as s: s.write(str(content)) # font.txtに出力 メインのコード image.py from PIL import Image, ImageTk, ImageOps, ImageDraw, ImageFont from io import BytesIO font = b'\x00\x01\x00\x00\x00\...' # ここにfont.txtの中身を貼り付け im = Image.new('RGB', (900, 600), "#ffffff") draw = ImageDraw.Draw(im) font = ImageFont.truetype(BytesIO(font),200) # フォントの読み込み draw.text((50,50), "font_test", fill="#000000", font=font) # 画像に文字を追加 im.save("./test.png") # 画像をtest.pngに出力 使い方 上のコードをコピペ read_font.pyの中のpath = "./font.ttf"の部分を読み込むフォントファイルのパスに書き換える read_font.pyを実行する font.txtができるのでimage.pyのfont = b'\x00\x01\x00\x00\x00\...'をfont.txtの中身に書き換える image.pyを実行する まとめ したかったことはできました 内容に不足や誤りがあれば補足します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python] 行列 ABC207D

ABC218C S と T に含まれる # の個数が異なる場合、答えは明らかに No です。そうでない場合を考えます。 S に対して 90 度回転を何回行うか 4 通りを全探索します。回転操作を施したあとのものを改めて S と呼ぶと、平行移動で S と T を一致させられるかどうかを判定すればよいです。 両者が一致するためには、S の最も左上のマスと T の最も左上のマスが一致することが必要であり、そのようなマスを求めることで平行移動量は一意に決まるため、平行移動により実際に一致するか判定すれば十分です。 以上により $O(N^2)$ で求めることができました。 サンプルコード def rot(S): return list(zip(*S[::-1])) def find_left_top(S): for i in range(N): for j in range(N): if S[i][j]=='#': return i,j def is_same(S,T): Si,Sj = find_left_top(S) Ti,Tj = find_left_top(T) offset_i = Ti-Si offset_j = Tj-Sj for i in range(N): for j in range(N): ii = i+offset_i jj = j+offset_j if 0<=ii<N and 0<=jj<N: if S[i][j]!=T[ii][jj]: return False else: if S[i][j]=='#': return False return True N = int(input()) S = [input() for _ in range(N)] T = [input() for _ in range(N)] cntS = sum(1 for i in range(N) for j in range(N) if S[i][j]=='#') cntT = sum(1 for i in range(N) for j in range(N) if T[i][j]=='#') if cntS != cntT: print("No") exit() for _ in range(4): if is_same(S,T): print("Yes") exit() S = rot(S) print("No") 解法は相応の時間で思い付いたのだが、実装が難しかった。 配列での行列回転処理が未熟なのが原因である。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Herokuの500エラーへの対応(Django)

Djangoで作成したアプリを2つHerokuにデプロイしたところ、 それぞれ500エラーが発生してしまったので、 その対応方法とエラーの内容について忘備録として残しておきます。 対応方法 結論から申し上げますと次のURLの通りの対応を行い、 Heroku上でもローカルと同じ方法でエラーの内容を確認できるようにしました。 Django Server Error (500)攻略法【2019 アドカレ】 アニメ一覧サイト「らごインフォ」作ってみた 具体的にはviews.pyとurls.pyに次の内容を加えます。 views.py from django.views.decorators.csrf import requires_csrf_token from django.http import HttpResponseServerError @requires_csrf_token def my_customized_server_error(request, template_name='500.html'): import sys from django.views import debug error_html = debug.technical_500_response(request, *sys.exc_info()).content return HttpResponseServerError(error_html) urls.py from [アプリ名] import views handler500 = views.my_customized_server_error このようにすると下の画像のようにエラー内容が表示されます。 エラー内容 1つ目のアプリ 上の画像のエラーが発生したアプリになります。 データベース関係のエラーでした。 herokuのデータベース設定を行った際に heroku run python manage.py makemigrations heroku run python manage.py migrate としていましたが、これだけではアプリ内に記述したmodels.pyの内容が何故か反映されていなかったため、 heroku run python manage.py makemigrations [アプリ名] heroku run python manage.py migrate としたところ、データベースの設定を無事に完了することができました。 2つ目のアプリ 1枚目と同じくデータベース関係のエラーでした。 500エラーの中身を検証したところ、 登録フォームからデータベースへの登録を行うと、 models.pyに記述した覚えのない列についてnull制約が発生し 登録できないエラーが発生していました。 heroku pg:reset DATABASE_URL と打ち込み、データベースの内容を全てリセットし、 再度設定したところ、無事に登録できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PyTorch 1.9.0] LSTMを使っていくつ先の未来まで精度良く予測できるのか検証してみた

目次 以下のこれら記事はPyTorchを使って時系列データの予測をしたい方に向けて書きました. 理論は本や論文をじっくり読む方が良いかと思いましたので,特に実装方法について詳しく書きました. 使用するプログラムの一部は各記事の中で重複しているものもあり,重複した部分を毎回1から説明すると記事が長くなり読みにくいと思うので省略しています. そのため以下の順番で読み進めていただけると読みやすいかと思います. [PyTorch 1.9.0] LSTMを使って時系列(単純な数式)予測してみた [PyTorch 1.9.0] LSTMを使っていくつ先の未来まで精度良く予測できるのか検証してみた <- 現在読んでいただいている記事 0. はじめに LSTMについて勉強していると先の未来を予測すれば予測するほど精度が落ちるという記述をみました. 確かに1個先の未来を予測するよりも10個先の未来を予測する方が難しいというのは直感的にわかります. ただ2,3個先の未来ならうまくいきそうですし,そのうまくいかない分水嶺ってどこにあるのかということを知りたくなりました. そこで簡単な時系列データを用いていくつ先の未来までだったら精度良く予測できるのか検証してみました. いくつか先の未来を予測する方法は主に2つあると考えられます. 1つ目は,学習時に正解ラベルを1つ先の未来の情報ではなく,いくつか先の未来の情報にして学習するという方法です. 2つ目は,正解ラベルを1つ先の未来の情報で学習したモデルを繰り返し使うという方法です. 比較のしやすさからこの記事では2つ目の方法を用いました. これはmany to many のシーケンスモデルです. 問題設定 sin関数やcos関数のような単純な数式を使って時系列データを生成し,その数式のいくつかの出力を用いて1~9つ先の未来の値を予測する問題を設定しました. イメージとしては以下の図の通りです. オレンジ色の点から緑の点を求める問題を設定しました. 精度検証にはRMSEを使いました. 1つ先の未来を予測したときのRMSEの値を基準として,その基準から何パーセントずれているのかで精度を評価しました. RMSEは,正解ラベルと予測値の二乗平均平方根誤差を計算しています. この値が小さければ小さいほどうまく当てはまっているモデルを学習できていると言えます. RMSEについて詳しく知りたい人は以下のサイトが参考になると思います. 3分でRMSEとRMSLEをサクッと入門!特徴と使い分け方まとめ 1. プログラムの概要 プログラムのほとんどのコードが前回の記事で紹介したコードと同じです. ただ前回のままだと1つ先の未来しか予測できないのでpred_result_pltの関数内の処理に改変を加えることで,任意の先の未来を予測できるようにしました. この記事で説明するコードの全体(折りたたんでいるのでこれをクリックして頂けると全体が見られます) またこの記事で説明するプログラムは,githubにもpredict_simple_formula_train_m_to_m.pyという名前でファイルをあげているので,見やすいほうを参照してください. 2. プログラムの説明 プログラムの概要の方でも言いましたが,pred_result_pltの関数以外は前回と同じなのでこの関数だけ説明しようと思います. この関数は,学習したモデルを使って1~任意の先の未来を予測し,正解ラベルと比較する処理を行っています. pred_result_pltの関数のコードの全体(折りたたんでいるのでこれをクリックして頂けると全体が見られます) def pred_result_plt(self, test_inputs, test_labels, test_times, sequence_length, input_size, pred_step_list): print('-------------') print("start predict test!!") self.net.eval() preds = [[] for num in range(len(pred_step_list))] rmses = [0.0 for num in range(len(pred_step_list))] for i in range(len(pred_step_list)): for j in range(0, len(test_inputs), pred_step_list[i]): input = np.array(test_inputs[j]).reshape(-1, sequence_length, input_size) for k in range(pred_step_list[i]): if j + k < test_labels.shape[0]: input_tensor = torch.Tensor(input).to(self.device) pred = self.net(input_tensor).data.cpu().numpy() input = np.delete(input, 0, axis=1) input = np.insert(input, 2, pred[0][0], axis=1) preds[i].append(pred[0][0]) preds[i] = np.array(preds[i]).reshape(-1) test_labels = test_labels.reshape(-1) rmses[i] = np.sqrt(np.sum(np.power((test_labels - preds[i]), 2)) / float(test_labels.shape[0])) print("pred_step = {}, rmse = {}, rmse_ratio = {}".format(i + 1, rmses[i], (rmses[i] - rmses[0]) / rmses[0])) #以下グラフ描画 plt_legend_list = ['label'] for i in range(len(pred_step_list)): plt.plot(test_times, preds[i]) plt_legend_list.append('pred_' + str(pred_step_list[i])) plt.plot(test_times, test_labels, c='#00ff00') plt.xlabel('t') plt.ylabel('y') plt.legend(plt_legend_list) plt.title('compare label and preds') plt.show() 以下のコードはこの引数でいくつ先の未来を予測したいか指定しています. このプログラムでは1~9つ先の未来を予測したいので[0,1,2,3,4,5,6,7,8,9]の配列を引数として渡します. 前回と違うのは引数にpred_step_listが追加されているところです. def pred_result_plt(self, test_inputs, test_labels, test_times, sequence_length, input_size, pred_step_list) 以下のコードは,予測した各出力と各RMSEを保存するためのリストを用意する処理を行っています. preds = [[] for num in range(len(pred_step_list))] rmses = [0.0 for num in range(len(pred_step_list))] 以下のコードは,先ほどの用意したリストに予測した出力を保存する処理を行っています. for i in range(len(pred_step_list))のiの最大値は予測したい未来の数です. 1~9つ先の未来を予測したいので9回ループを回しpredsのリストに予測した出力を9回appendします. for j in range(0, len(test_inputs), pred_step_list[i])のjの最大値はテスト用の入力データの数です. また3つ目の引数のpred_step_list[i]は予測したい未来の先によって使うテスト用の入力データを変えるためのものです. 例えばi=0つまりpred_step_list[i]=1の時は,普通のforループのように1つ飛ばしでループを回します. この時使うテスト用の入力データは,[[test0,test1,test2][test1,test2,test3]...[test47,test48,test49]]となります. 次にi=4つまりpred_step_list[i]=4の時は普通のforループとは違い3つ飛ばしでループを回します. この時4つ飛ばしのため使うテスト用の入力データは,[[test0,test1,test2][test4,test5,test6]...[test43,test44,test45]]となります. これはなぜこのようなforループの値を飛ばす処理をするのかというと4つ先の未来を予測するときは予測した出力を入力として使いまわすからです. 4つ先の未来を予測するときはまず[test0,test1,test2]の入力データから1つ先の未来のtest3を予測します.(予測したtest3をtest3_predと呼ぶことにします.) test3_predを使い[test1,test2,test3_pred]という入力データを作り2つ先の未来のtest4を予測します. これを繰り返し4つ先の未来のtest6まで予測します. よって次に予測したいのはtest7であり,次に使うテスト用の入力データは[test4,test5,test6]が良く,forループの値を飛ばす処理が必要です. for k in range(pred_step_list[i])のkの最大値はiの時の予測幅です. このループの中で予測した値を使いまわす処理を行います. 予測した値を使いまわすため,まずnp.delete(input, 0, axis=1)で入力データの先頭を消去します. そしてnp.insert(input, 2, pred[0][0], axis=1)で入力データの末尾に予測値を追加します. for i in range(len(pred_step_list)): for j in range(0, len(test_inputs), pred_step_list[i]): input = np.array(test_inputs[j]).reshape(-1, sequence_length, input_size) for k in range(pred_step_list[i]): if j + k < test_labels.shape[0]: input_tensor = torch.Tensor(input).to(self.device) pred = self.net(input_tensor).data.cpu().numpy() input = np.delete(input, 0, axis=1) input = np.insert(input, 2, pred[0][0], axis=1) preds[i].append(pred[0][0]) 以下のコードは,RMSEを計算する処理を行っています. reshape(-1)でpreds[i]とtest_labelsの配列の形を揃えます. preds[i] = np.array(preds[i]).reshape(-1) test_labels = test_labels.reshape(-1) rmses[i] = np.sqrt(np.sum(np.power((test_labels - preds[i]), 2)) / float(test_labels.shape[0])) print("pred_step = {}, rmse = {}, rmse_ratio = {}".format(i + 1, rmses[i], (rmses[i] - rmses[0]) / rmses[0])) 以下のコードは,正解ラベルと予測した1~9つ先の値を重ねてグラフに表示する処理を行っています. #以下グラフ描画 plt_legend_list = ['label'] for i in range(len(pred_step_list)): plt.plot(test_times, preds[i]) plt_legend_list.append('pred_' + str(pred_step_list[i])) plt.plot(test_times, test_labels, c='#00ff00') plt.xlabel('t') plt.ylabel('y') plt.legend(plt_legend_list) plt.title('compare label and preds') plt.show() 3. 実行結果 このプログラムを実行すると以下の図をプロットし,ターミナル上に予測した値から計算したRMSEの値を出力します.(calc_modeの変数でsin関数を予測するのかcos関数を予測するのか選択する必要があります) 図を見るとずれ大きく見えるものはいくつかあるが,振幅の2倍3倍ほど大きなずれはなく悪くない結果に見えます. 以下はsin関数の各予測した未来の幅ごとのRMSEと1つ先の未来を予測した時のRMSEを基準とした割合です. RMSEの割合を見てみると,2つ先を予測した時点で+56.65479237021891%で,最大値は9つ予測した時の+578.8903915940437%でした. また例外はあるものの割合は大体単調増加しています. 図を見ると悪くなさそうに見えますがRMSEの割合を見てみるとかなり悪そうに見えます. 数値で見ると人間の感覚では違いが良く判らないものも評価できるので良いです. pred_step = 1, rmse = 0.0463193382933841, rmse_ratio = 0.0 pred_step = 2, rmse = 0.07256146323076017, rmse_ratio = 0.5665479237021891 pred_step = 3, rmse = 0.10495637762864339, rmse_ratio = 1.2659299872518808 pred_step = 4, rmse = 0.12224027240570762, rmse_ratio = 1.639076396805251 pred_step = 5, rmse = 0.16198444770642542, rmse_ratio = 2.4971235271200327 pred_step = 6, rmse = 0.13712321704460165, rmse_ratio = 1.9603880818864647 pred_step = 7, rmse = 0.22368511340234334, rmse_ratio = 3.829194924710157 pred_step = 8, rmse = 0.24009131197388198, rmse_ratio = 4.1833925271806995 pred_step = 9, rmse = 0.31445753712372515, rmse_ratio = 5.788903915940437 以下はcos関数の各予測した未来の幅ごとのRMSEと1つ先の未来を予測した時のRMSEを基準とした割合です. sin関数の時と同様にRMSEの割合大体単調増加していて,割合のずれは大きく見えます. pred_step = 1, rmse = 0.044212785920495934, rmse_ratio = 0.0 pred_step = 2, rmse = 0.06760037896588772, rmse_ratio = 0.5289780446644482 pred_step = 3, rmse = 0.0988830401746241, rmse_ratio = 1.2365258853499301 pred_step = 4, rmse = 0.13131762236247768, rmse_ratio = 1.9701277498915113 pred_step = 5, rmse = 0.1562937218522084, rmse_ratio = 2.535034461145652 pred_step = 6, rmse = 0.1124458296842421, rmse_ratio = 1.5432875884917954 pred_step = 7, rmse = 0.19061638995942234, rmse_ratio = 3.3113408483733977 pred_step = 8, rmse = 0.24447076357429492, rmse_ratio = 4.52941323385289 pred_step = 9, rmse = 0.2027480673314639, rmse_ratio = 3.585733812296931 6. おわりに 1つ先の未来を予測したときのRMSEの値を基準に考えると,精度良く予測できるのは+100%を超えていない2つ先までなのかなと思いました. ただあくまでもこのプログラムの実験環境,パラメータの中でそうだというだけでもう少しパラメータをいじると結果は変わると思います. 理解が浅いところもあるので,いろいろ間違った理解や用語の使い方をしているかもしれないです. 遠慮なくご指摘頂けると幸いです. またこの記事で説明したプログラムファイル(predict_simple_formula_train_m_to_m.py)やanacondaの環境ファイル(predict_simple_formula_env.yml)は以下のgithubにもあげているので良かったら見ていってください. 7. 参考にさせていただいたサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでのデータの前処理、やってみたら案外 何とかなるよ

~データ前処理に対する抵抗感を払しょくしてやる!・・・やってみたら案外いけるよ編~ はじめに   Pycaretというライブラリはご存じだろうか? なんと、データの前処理までも自動でやってくれる! これじゃあ人間の出る幕がないじゃないか・・・でも試したいということで、以下のチュートリアルコンペのデータをPycaret任せでデータ分析し、submitしてみました。 結果は、まぁぜんぜんダメ(3759位/4650位中)でした(^^;)。 機械は大いに活用すべきですが、「任せっきりではダメなんだな」とあらためて痛感し、ある程度のデータの前処理はできるようになってやるぞ!というのが、この記事のテーマです。   前処理のそれぞれの方法を機械的に捉えると、たぶんわかんなくなっちゃうので、具体例で実行した前処理の過程を残すことにしました。   実行条件など ・Google colabで実行 ・Kaggleのタイタニックデータで実行 タイタニックのデータセットについて 以下サイト(Kaggle)の「train.csv」を使わせていただいた。 タイタニックデータセットの項目と内容 項目 内容 PassengerId 乗客ユニークID Survived 生存フラグ(0=死亡,1=生存) Pclass チケットクラス(1=上級,2=中級,3=下級) Name 乗客名 Sex 性別(male=男性,female=女性) Age 年齢 SibSp 同乗している兄弟,配偶者の数 parch 同乗している親,子供の数 ticket チケット番号 fare 料金 cabin 客室番号 Embarked 出港地(C=Cherbourg,Q=Queenstown,S=Southampton) 前処理してみよう! 1. ライブラリのインストールおよびインポート 以前もある記事で描いたが、使用するライブラリをいちいち検討するのは面倒なので、基本以下としています。乱暴ですいません。 ライブラリのインストールおよびインポート pip install japanize-matplotlib # Import required libraries %matplotlib inline import pandas as pd import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib import seaborn as sns sns.set(font="IPAexGothic") from scipy.stats import norm 2. データの読み込み df=pd.read_csv('train.csv') df.head() 3. データ確認 まず、データのカラム、欠損値有無、データ型を確認しました。 df.info() 各カラムの欠損数も確認しました。 Age、Cabin、Embarkedには欠損値があり、何らかの処置が必要なことがわかります。 参考 ここではこれ以上データ確認については述べませんが、以下ライブラリが便利がと思います。 4. よくわからないデータは生データを確認 欠損値があったCabinはどのようなデータなのかについて見てみました。 df['Cabin'].value_counts() Cabinの中身(データ)はバラバラですね。 一応グラフ化もしてみましたが、これから特徴や傾向をつかむことはむつかいように思います。 sns.countplot(y="Cabin", data=df) df.head() 5. データ分析に関連しない列を削除 次に、カテゴリー化できないものや、データ分析に関連しないデータは列ごと削除しました。 ここでは、先ほどのCabinと、objectデータのName,Ticketは(データ分析には活かせそうにないので)列ごと削除することにしました。 # 特定列削除 df.drop("Cabin", axis = 1, inplace = True) df.head() # 特定列削除 df.drop("Name", axis = 1, inplace = True) df.head() # 特定列削除 df.drop("Name", axis = 1, inplace = True) df.head() ずいぶん、すっきりしました。     6. 欠損値を補う 再度、データのカラム、欠損値有無、データ型を確認しました。 項目数は11→8となり、すこしスッキリしました。 次は、AgeとEmbarkedの欠損値処理を行うことにします。 まずはAgeです。ヒストグラムでこのデータの傾向をみてみます。一山の分布となっています。ピークはすこし左よりの分布となっているように思います。 sns.distplot( df['Age'],bins=10, color='#123456',label='data', kde_kws={'label': 'kde','color':'k'}, fit=norm,fit_kws={'label': 'norm','color':'red'}, rug=False ) plt.legend() plt.grid() plt.show() 左よりの分布を考慮し、欠損値には「中央値」を補完しました。 df['Age'].fillna(df['Age'].median(), inplace=True) 再度、データのカラム、欠損値有無、データ型を確認してみます。 Ageの欠損値はなくなりました。 df.info() 次は、Embarkedです。まず傾向をグラフで見てみました。これは欠損値以外が表示されたものですが、S,C,Q の3パターンのでデータであることがわかります。 sns.countplot(y="Embarked", data=df) Embarkedの欠損値処理は、もっとも多い「S」に補完しました。以下infoの通り、補完によって欠損値がなくなったことがわかります。 df["Embarked"] = df["Embarked"].fillna("S") df.info() 実務では、中途半端に欠損値を補ったりせず、欠損データは削除することも多いでしょう。以下はそのような場合のコードです。 # ある行のすべての値が欠損していたら、その行を削除する df.dropna(subset=['列名'], how='all') # すべての欠損値を0で置換する df.fillna(0)   7. ラベルエンコーディング(カテゴリーデータの数値変換) SexとEmbarkedはカテゴリーデータなので、ラベルエンコーディングを行いました。まずはSexです。以下のコードの実行でmale=0に、female=1となりました。 # LabelEncoder from sklearn.preprocessing import LabelEncoder le = LabelEncoder() le.fit(df['Sex']) df['Sex'] = le.transform(df['Sex']) df.head() つぎはEmbarkedです。以下のコードの実行でEmbarkedも0,1,2にエンコーディングできました。 ``` LabelEncoder from sklearn.preprocessing import LabelEncoder le = LabelEncoder() le.fit(df['Embarked']) df['Embarked'] = le.transform(df['Embarked']) df.head() ``` 8. 新たな特徴量の生成 SibSpは、兄弟,配偶者の数。 Parchは、両親,子供の数 です。この2つの説明変数はいわば家族の数です。またこの2つ相関も高く、このまま分析に使うのはよくありませんので、足し合わせて新しい変数にしました。(これは「Kaggleスタートブック」に紹介されていた内容です) df['FamilySize'] = df['Parch'] + df['SibSp'] + 1 + 1 sns.countplot(y='FamilySize', data = df, hue='Survived') df.head() これも「Kaggleスタートブック」に紹介されていた内容ですが、FamilySizeが1という方が大部分です。またグラフの通り、FamilySizeが1の方は生存率が低い傾向もみられ案す。このことからFamilySizeが1であるか否かを特徴として取り上げてもよさそうです、 df['IsAlone'] = 0 df.loc[df['FamilySize'] == 1, 'IsAlone'] = 1 df.head() 最後に、FamilySizeに統合したSibSpとParchのデータは列ごと削除しました。 # 特定列削除 df.drop("SibSp", axis = 1, inplace = True) df.head() # 特定列削除 df.drop("Parch", axis = 1, inplace = True) df.head() df.info() df.isnull().sum() これで欠損値もなくなり、前処理は無事に終了しました。 最後に 前処理はとても億劫であったが、やってみたら・・・まぁやれなくないなという感じです。  これで完璧!といえるかどうかは別として、このあたりまで処理した後、Pycaret活用・・・といった流れの方が現実的なのかもしれません。   参考サイト 参考書籍
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでのデータの前処理、やってみたら案外 何とかなったよ

~データ前処理に対する抵抗感を払しょくしてやる!・・・やってみたら案外いけるよ編~ はじめに   Pycaretというライブラリはご存じだろうか? なんと、データの前処理までも自動でやってくれる! これじゃあ人間の出る幕がないじゃないか・・・でも試したいということで、以下のチュートリアルコンペのデータをPycaret任せでデータ分析し、submitしてみました。 結果は、まぁぜんぜんダメ(3759位/4650位中)でした(^^;)。 機械は大いに活用すべきですが、「任せっきりではダメなんだな」とあらためて痛感し、ある程度のデータの前処理はできるようになってやるぞ!というのが、この記事のテーマです。   前処理のそれぞれの方法を機械的に捉えると、たぶんわかんなくなっちゃうので、具体例で実行した前処理の過程を残すことにしました。   実行条件など ・Google colabで実行 ・Kaggleのタイタニックデータで実行 タイタニックのデータセットについて 以下サイト(Kaggle)の「train.csv」を使わせていただいた。 タイタニックデータセットの項目と内容 項目 内容 PassengerId 乗客ユニークID Survived 生存フラグ(0=死亡,1=生存) Pclass チケットクラス(1=上級,2=中級,3=下級) Name 乗客名 Sex 性別(male=男性,female=女性) Age 年齢 SibSp 同乗している兄弟,配偶者の数 parch 同乗している親,子供の数 ticket チケット番号 fare 料金 cabin 客室番号 Embarked 出港地(C=Cherbourg,Q=Queenstown,S=Southampton) 前処理してみよう! 1. ライブラリのインストールおよびインポート 以前もある記事で描いたが、使用するライブラリをいちいち検討するのは面倒なので、基本以下としています。乱暴ですいません。 ライブラリのインストールおよびインポート pip install japanize-matplotlib # Import required libraries %matplotlib inline import pandas as pd import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib import seaborn as sns sns.set(font="IPAexGothic") from scipy.stats import norm 2. データの読み込み df=pd.read_csv('train.csv') df.head() 3. データ確認 まず、データのカラム、欠損値有無、データ型を確認しました。 df.info() 各カラムの欠損数も確認しました。 Age、Cabin、Embarkedには欠損値があり、何らかの処置が必要なことがわかります。 参考 ここではこれ以上データ確認については述べませんが、以下ライブラリが便利がと思います。 4. よくわからないデータは生データを確認 欠損値があったCabinはどのようなデータなのかについて見てみました。 df['Cabin'].value_counts() Cabinの中身(データ)はバラバラですね。 一応グラフ化もしてみましたが、これから特徴や傾向をつかむことはむつかいように思います。 sns.countplot(y="Cabin", data=df) df.head() 5. データ分析に関連しない列を削除 次に、カテゴリー化できないものや、データ分析に関連しないデータは列ごと削除しました。 ここでは、先ほどのCabinと、objectデータのName,Ticketは(データ分析には活かせそうにないので)列ごと削除することにしました。 # 特定列削除 df.drop("Cabin", axis = 1, inplace = True) df.head() # 特定列削除 df.drop("Name", axis = 1, inplace = True) df.head() # 特定列削除 df.drop("Name", axis = 1, inplace = True) df.head() ずいぶん、すっきりしました。     6. 欠損値を補う 再度、データのカラム、欠損値有無、データ型を確認しました。 項目数は11→8となり、すこしスッキリしました。 次は、AgeとEmbarkedの欠損値処理を行うことにします。 まずはAgeです。ヒストグラムでこのデータの傾向をみてみます。一山の分布となっています。ピークはすこし左よりの分布となっているように思います。 sns.distplot( df['Age'],bins=10, color='#123456',label='data', kde_kws={'label': 'kde','color':'k'}, fit=norm,fit_kws={'label': 'norm','color':'red'}, rug=False ) plt.legend() plt.grid() plt.show() 左よりの分布を考慮し、欠損値には「中央値」を補完しました。 df['Age'].fillna(df['Age'].median(), inplace=True) 再度、データのカラム、欠損値有無、データ型を確認してみます。 Ageの欠損値はなくなりました。 df.info() 次は、Embarkedです。まず傾向をグラフで見てみました。これは欠損値以外が表示されたものですが、S,C,Q の3パターンのでデータであることがわかります。 sns.countplot(y="Embarked", data=df) Embarkedの欠損値処理は、もっとも多い「S」に補完しました。以下infoの通り、補完によって欠損値がなくなったことがわかります。 df["Embarked"] = df["Embarked"].fillna("S") df.info() 実務では、中途半端に欠損値を補ったりせず、欠損データは削除することも多いでしょう。以下はそのような場合のコードです。 # ある行のすべての値が欠損していたら、その行を削除する df.dropna(subset=['列名'], how='all') # すべての欠損値を0で置換する df.fillna(0)   7. ラベルエンコーディング(カテゴリーデータの数値変換) SexとEmbarkedはカテゴリーデータなので、ラベルエンコーディングを行いました。まずはSexです。以下のコードの実行でmale=0に、female=1となりました。 # LabelEncoder from sklearn.preprocessing import LabelEncoder le = LabelEncoder() le.fit(df['Sex']) df['Sex'] = le.transform(df['Sex']) df.head() つぎはEmbarkedです。以下のコードの実行でEmbarkedも0,1,2にエンコーディングできました。 ``` LabelEncoder from sklearn.preprocessing import LabelEncoder le = LabelEncoder() le.fit(df['Embarked']) df['Embarked'] = le.transform(df['Embarked']) df.head() ``` 8. 新たな特徴量の生成 SibSpは、兄弟,配偶者の数。 Parchは、両親,子供の数 です。この2つの説明変数はいわば家族の数です。またこの2つ相関も高く、このまま分析に使うのはよくありませんので、足し合わせて新しい変数にしました。(これは「Kaggleスタートブック」に紹介されていた内容です) df['FamilySize'] = df['Parch'] + df['SibSp'] + 1 + 1 sns.countplot(y='FamilySize', data = df, hue='Survived') df.head() これも「Kaggleスタートブック」に紹介されていた内容ですが、FamilySizeが1という方が大部分です。またグラフの通り、FamilySizeが1の方は生存率が低い傾向もみられ案す。このことからFamilySizeが1であるか否かを特徴として取り上げてもよさそうです、 df['IsAlone'] = 0 df.loc[df['FamilySize'] == 1, 'IsAlone'] = 1 df.head() 最後に、FamilySizeに統合したSibSpとParchのデータは列ごと削除しました。 # 特定列削除 df.drop("SibSp", axis = 1, inplace = True) df.head() # 特定列削除 df.drop("Parch", axis = 1, inplace = True) df.head() df.info() df.isnull().sum() これで欠損値もなくなり、前処理は無事に終了しました。 ここでは「PassengerId」は残していますが、これも削除して問題ないと思います。 最後に 前処理はとても億劫であったが、やってみたら・・・まぁやれなくないなという感じです。  これで完璧!といえるかどうかは別として、このあたりまで処理した後、Pycaret活用・・・といった流れの方が現実的なのかもしれません。   参考サイト 参考書籍
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PySparkにて階層されているStructTypeを単一のカラムにフラット化する方法

概要 PySparkにて階層されているStructTypeを単一のカラムにフラットする方法を共有します。 階層されているStructTypeとは、下記のデータフレームのstruct列のことです。 今回紹介するのは、structのカラムをstruct.strintg.in.structという単一のカラムにする方法です。 検証環境 databricks runtime: 8.3.x-cpu-ml-scala2.12 Python version: 3.8.8 pyspark version: 3.1.2.dev0 手順 1. データを準備 json_data =""" { "array":[ { "strintg.in.array": "値①"}, { "strintg.in.array": "値②"}, { "strintg.in.array": "値③"} ], "number":100, "string":"日本語", "struct":{ "strintg.in.struct": "struct_1" }, } """ df = spark.read.json(sc.parallelize([json_data])) df.display() df.createOrReplaceTempView('tmp_nested_columns') df.printSchema() 2. 関数を定義 def get_flatten_transformation(df:DataFrame, combine_str = '_'): """StructTypeの階層をフラット化 """ cols=[col(e['name']).alias(e['renamed_column_name']) for e in _get_defination(df.schema.jsonValue(), combine_str=combine_str)] return df.select(cols) def _get_defination(schema:dict, prefix='', combine_str='_'): """Sparkデータフレームのスキーマを辞書の変数としてリターン値 """ fields = schema['fields'] list_definations=[] prefix = prefix+'.' if prefix !='' else '' for field in fields: if type(field['type']) is dict: if field['type']['type'] == 'struct': column_name_prefix = f'{prefix}`{field["name"]}`' list_definations.extend(DataEngineering.get_defination(field['type'], column_name_prefix)) else: field["name"] = f'{prefix}`{field["name"]}`' field["renamed_column_name"] = f'{field["name"]}'.replace('.', combine_str).replace('`', '') list_definations.append(field) else: field["name"] = f'{prefix}`{field["name"]}`' field["renamed_column_name"] = f'{field["name"]}'.replace('.', combine_str).replace('`', '') list_definations.append(field) return list_definations 3. 階層されているStructTypeをフラット化 df_2 = get_flatten_transformation(df) df_2.printSchema() df_2.display() 4. ArrayTypeのデータフレームをフラット化 # ArrayTypeをexplode関数によりStructTypeに変換 df_2_explode = (df .select('*',explode('array').alias('_array')) .drop('array') .withColumnRenamed('_array','array') ) df_2_explode.display() df_2_explode.printSchema()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PySparkにて階層されているStructTypeを単一のカラムにフラットする方法

概要 PySparkにて階層されているStructTypeを単一のカラムにフラットする方法を共有します。 階層されているStructTypeとは、下記のデータフレームのstruct列のことです。 今回紹介するのは、structのカラムをstruct.strintg.in.structという単一のカラムにする方法です。 検証環境 databricks runtime: 8.3.x-cpu-ml-scala2.12 Python version: 3.8.8 pyspark version: 3.1.2.dev0 手順 1. データを準備 json_data =""" { "array":[ { "strintg.in.array": "値①"}, { "strintg.in.array": "値②"}, { "strintg.in.array": "値③"} ], "number":100, "string":"日本語", "struct":{ "strintg.in.struct": "struct_1" }, } """ df = spark.read.json(sc.parallelize([json_data])) df.display() df.createOrReplaceTempView('tmp_nested_columns') df.printSchema() 2. 関数を定義 def get_flatten_transformation(df:DataFrame, combine_str = '_'): """StructTypeの階層をフラット化 """ cols=[col(e['name']).alias(e['renamed_column_name']) for e in _get_defination(df.schema.jsonValue(), combine_str=combine_str)] return df.select(cols) def _get_defination(schema:dict, prefix='', combine_str='_'): """Sparkデータフレームのスキーマを辞書の変数としてリターン値 """ fields = schema['fields'] list_definations=[] prefix = prefix+'.' if prefix !='' else '' for field in fields: if type(field['type']) is dict: if field['type']['type'] == 'struct': column_name_prefix = f'{prefix}`{field["name"]}`' list_definations.extend(DataEngineering.get_defination(field['type'], column_name_prefix)) else: field["name"] = f'{prefix}`{field["name"]}`' field["renamed_column_name"] = f'{field["name"]}'.replace('.', combine_str).replace('`', '') list_definations.append(field) else: field["name"] = f'{prefix}`{field["name"]}`' field["renamed_column_name"] = f'{field["name"]}'.replace('.', combine_str).replace('`', '') list_definations.append(field) return list_definations 3. 階層されているStructTypeをフラット化 df_2 = get_flatten_transformation(df) df_2.printSchema() df_2.display() 4. ArrayTypeのデータフレームをフラット化 # ArrayTypeをexplode関数によりStructTypeに変換 df_2_explode = (df .select('*',explode('array').alias('_array')) .drop('array') .withColumnRenamed('_array','array') ) df_2_explode.display() df_2_explode.printSchema()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】argparse で `--help` や `--version` のようなコマンドを実装する

argparse を使って、一般的な CLI における --help や --version のような動きをするコマンドを実装してみました。 具体的には以下の要件です。 他の引数やオプションよりも優先して実行される 実行された後、即時終了する 実行環境 Python 3.9.7 で正常に実行されることを確認しました。ある程度新しいバージョンであれば動くと思います。 サンプルスクリプト -S もしくは --system をオプションとして与えられたとき、print(sys.version) を実行して処理を終了するシンプルなスクリプトです。 sample.py import sys from argparse import SUPPRESS, Action, ArgumentParser def show_system_version(): print(sys.version) class SampleAction(Action): def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, help=None): super().__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, help=help, ) def __call__(self, parser, namespace, values, option_string=None): show_system_version() parser.exit() def main(): parser = ArgumentParser() parser.add_argument( "-S", "--system", action=SampleAction, help="show system version and exit" ) parser.add_argument("--mushi", help="無視されるべきオプション") args = parser.parse_args() print(args.mushi) if __name__ == "__main__": main() show_system_version 関数の中身やヘルプメッセージを変更するなどしてカスタマイズできます。 ポイントは以下です。 Action クラスを継承したクラスを作成する __call__ 内で処理を実行し、 parser.exit() する add_argument の action にそのクラスを指定する 実装の詳細は、argparse モジュール の _HelpAction クラスと _VersionAction クラスを参考にしました。 サンプルスクリプトの実行結果 # ヘルプテキストが正常に表示されることを確認 $ python3 sample.py -h usage: sample.py [-h] [-S] [--mushi MUSHI] optional arguments: -h, --help show this help message and exit -S, --system show system version and exit --mushi MUSHI 無視されるべきオプション # print(sys.version) が正常に実行されることを確認 $ python3 sample.py -S 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] # -S をつけないと --mushi 引数のテキストは表示される $ python3 sample.py --mushi テキスト テキスト # -S をつけると --mushi は無視される $ python3 sample.py --mushi テキスト -S 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] # 順番を変更しても上記と同じ結果 $ python3 sample.py -S --mushi テキスト 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] 補足 サンプルスクリプト内で sys.version を表示していますが、実際にパッケージやライブラリのバージョンを表示する --version コマンドを実装する場合は、add_argument('--version', action='version', version='%(prog)s 2.0') のような専用アクションが用意されていますので、そちらを使用すべきです。 参考リンク argparse --- action — Python 3.9.4 ドキュメント argparse --- Action クラス — Python 3.9.4 ドキュメント 終わりに 記事内に誤りがある場合や「もっとうまいやり方があるよ」という場合は、コメント頂けますと幸いです。 また、Qiita 初投稿ですので、記事の書き方の作法的な部分でのご指摘もお待ちしております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】argparse で `--help` や `--version` のようなコマンドを自作する

argparse を使って、一般的な CLI における --help や --version のような動きをするコマンドを実装してみました。 具体的には以下の要件です。 他の引数やオプションよりも優先して実行される 実行された後、即時終了する 実行環境 Python 3.9.7 で正常に実行されることを確認しました。ある程度新しいバージョンであれば動くと思います。 サンプルスクリプト -S もしくは --system をオプションとして与えられたとき、print(sys.version) を実行して処理を終了するシンプルなスクリプトです。 sample.py import sys from argparse import SUPPRESS, Action, ArgumentParser def show_system_version(): print(sys.version) class SampleAction(Action): def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, help=None): super().__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, help=help, ) def __call__(self, parser, namespace, values, option_string=None): show_system_version() parser.exit() def main(): parser = ArgumentParser() parser.add_argument( "-S", "--system", action=SampleAction, help="show system version and exit" ) parser.add_argument("--mushi", help="無視されるべきオプション") args = parser.parse_args() print(args.mushi) if __name__ == "__main__": main() show_system_version 関数の中身やヘルプメッセージを変更するなどしてカスタマイズできます。 ポイントは以下です。 Action クラスを継承したクラスを作成する __call__ 内で処理を実行し、 parser.exit() する add_argument の action にそのクラスを指定する 実装の詳細は、argparse モジュール の _HelpAction クラスと _VersionAction クラスを参考にしました。 サンプルスクリプトの実行結果 # ヘルプテキストが正常に表示されることを確認 $ python3 sample.py -h usage: sample.py [-h] [-S] [--mushi MUSHI] optional arguments: -h, --help show this help message and exit -S, --system show system version and exit --mushi MUSHI 無視されるべきオプション # print(sys.version) が正常に実行されることを確認 $ python3 sample.py -S 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] # -S をつけないと --mushi 引数のテキストは表示される $ python3 sample.py --mushi テキスト テキスト # -S をつけると --mushi は無視される $ python3 sample.py --mushi テキスト -S 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] # 順番を変更しても上記と同じ結果 $ python3 sample.py -S --mushi テキスト 3.9.7 (default, Sep 3 2021, 12:37:55) [Clang 12.0.5 (clang-1205.0.22.9)] 補足 サンプルスクリプト内で sys.version を表示していますが、実際にパッケージやライブラリのバージョンを表示する --version コマンドを実装する場合は、add_argument('--version', action='version', version='%(prog)s 2.0') のような専用アクションが用意されていますので、そちらを使用すべきです。 参考リンク argparse --- action — Python 3.9.4 ドキュメント argparse --- Action クラス — Python 3.9.4 ドキュメント 終わりに 記事内に誤りがある場合や「もっとうまいやり方があるよ」という場合は、コメント頂けますと幸いです。 また、Qiita 初投稿ですので、記事の書き方の作法的な部分でのご指摘もお待ちしております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC218 A~E問題 ものすごく丁寧でわかりやすい解説 python 灰色~茶色コーダー向け #AtCoder

ABC218(AtCoder Beginner Contest 218) A~E問題の解説記事です。 灰色~茶色コーダーの方向けに解説しています。 その他のABC解説、動画などは以下です。 A - Weather Forecast N文字目が○ならばYes、xならばNoを出力します。 if文で分岐して出力すればよいです。 以下の点に注意してください。 ・pythonの文字列は最初が「0文字目」  たとえば「oxxxoox」を受け取った時、「0文字目」=S[0]はo、「1文字目」=S[1]はx、...になります。  よって問題文で言うN文字目はpythonでは「N-1文字目」=S[N-1]となります。 ・文字列は""で囲う  以下のように書くとエラーになります。 【間違い】 if S[N-1]==o: print(Yes) else: print(No) ○、Yes、Noはそれぞれ文字列であるため、"○"、"Yes"、"No"としましょう。 【正解】 if S[N-1]=="o": print("Yes") else: print("No") 入力の受け取り、出力がわからない方は以下の記事を参考にしてください。 【提出】 # 入力の受け取り # Nは数字 N=int(input()) # Sは文字列 S=input() # SのN文字目(S[N-1])が"o"なら if S[N-1]=="o": # Yesを出力 print("Yes") # そうでないならば(SのN文字目(S[N-1])が"x"なら) else: # Noを出力 print("No") B - qwerty 1→A 2→B ... と変換するような関数を作っても解けますが26個条件分岐を書くのは面倒くさいです。 コンピュータで扱う文字には文字コードという番号がついており、pythonでは 文字コード→文字 への変換ができるchrという関数があります。 chr(文字コード番号)=文字 例えば『a』の文字コードは97なので chr(97)="a" となります。 a,b,c,...の文字コードは97,98.99,...と連番になっているため、chr(Pの値+96)とすれば欲しい文字へ変換ができます。 【提出】 # 入力の受け取り P=list(map(int, input().split())) # 答えを格納する変数 ans="" # P1~P26(P[0]~P[25])まで for x in P: # 文字コードx+96の文字をansの末尾へ追加 ans+=chr(x+96) # 答えを出力 print(ans) C - Shapes C問題にしては高難易度の問題でした。 コンテスト参加中にC問題がなかなか解けないとなったらかなり焦りますが、諦めてさっさとD、E問題へ進みましょう。 実際今回のコンテストはC問題よりもD、E問題を解けた人が多く、Cを早めに諦めた人のほうがパフォーマンスが高くなりました。 まずこういったグリッドを与えられる問題を初めて見た人は入力の受け取りに戸惑うと思います。 以下の手順で受け取りましょう。 (1)空のリストを用意(S) (2)一行ずつ文字列で受け取り(S_tmp) (3)文字列をリストへ変換(1文字ずつのリストになります) (4)変換したリストをSへ追加 # N行受け取り for i in range(N): # 文字列でSを受け取り S_tmp=input() # リストに変換 S_tmp=list(S_tmp) # Sへ格納 S.append(S_tmp) こうしてやるとS[行番号][列番号]としてそれぞれのマスが「.」か「#」か確認できます。 解法は以下の手順です。 (1)シャープの個数を数える S、Tのシャープの個数を数えましょう。個数が違っていたらなにをしようが一致しないので「No」を出力して終了です。 (2)回転する 次に回転ですがこれは4通り(90度、180度、270度、360(0)度)しかないのですべて試してみればOKです。 回転する、というのがどういう座標の変換になるのか考えましょう。 図の赤色の部分に注目しましょう。 (行番号,列番号)として (3,0) (0,1) (1,4) (4,3) と変化していきます。 「回転前の列番号」が「回転後の行番号」になるのはなんとなくわかりますね。 「回転後の列番号」は少し分かりづらいですが「4-回転前の行番号」になっています。 この4というのは0始まりにしたときの行、列の大きさ、つまりN-1です。 まとめると90度時計回りに回転するというのは (行番号,列番号)→(列番号,(N-1)-行番号) と変換する、ということになります。 (3)平行移動する 回転していい感じに図のようになったとします。 まず左上からマスを見ていき、初めて#がでてきた座標を確認します。図の青い部分です。 もし図形の形が一致しているなら、ここを合わせるように平行移動すれば完全一致するはずです。 ですので(0,1)→(3,3)となるように平行移動するわけですがこれは簡単で 行:下方向に3(3-0)→行+3 列:右方向に2(3-1)→列+2 となります まとめると平行移動は 左上からはじめてシャープの出る行、列番号の差を確認し、 (行番号,列番号)→(行番号+下方向への移動量,列番号+右方向への移動量) と変換する、ということになります。 実装の際はそれぞれを行う関数を用意すると楽です。 すなわち以下5つを作ります。 ・#の個数を確認する関数:count_sharp(X) ・90度時計回りに回転する関数:rotate(X) ・左上から探索して初めて#が出てくる行番号,列番号を返す関数:first_sharp(X) ・右方向へmove_gyou,下方向へmove_retu平行移動する関数:Translation(X,move_gyou,move_retu) ・S,Tが一致しているか確認する関数:check(S,T) 【提出】 # #の個数を確認する関数 def count_sharp(X): # #の個数をカウントする変数 count=0 # 行0~Nまで for gyou in range(N): # 列0~Nまで for retu in range(N): # もしx[行番号][列番号]が#なら if X[gyou][retu]=="#": # カウントにプラス1 count+=1 # カウント数を返す return count # 90度時計回りに回転する関数 def rotate(X): # 回転後のグリッド rotate_X=[["."]*N for i in range(N)] # 行0~Nまで for gyou in range(N): # 列0~Nまで for retu in range(N): # (行番号,列番号)→(列番号,(N-1)-行番号) rotate_X[retu][N-1-gyou]=X[gyou][retu] # 回転後のグリッドを返す return rotate_X # 左上から探索して初めて#が出てくる行番号,列番号を返す関数 def first_sharp(X): # 行0~Nまで for gyou in range(N): # 列0~Nまで for retu in range(N): # もしx[行番号][列番号]が#なら if X[gyou][retu]=="#": # 行番号,列番号を返して終了 return gyou,retu # 下方向へmove_gyou,右方向へmove_retu平行移動する関数 def Translation(X,move_gyou,move_retu): # 平行移動後のグリッド move_X=[["."]*N for i in range(N)] # 行0~Nまで for gyou in range(N): # 列0~Nまで for retu in range(N): # 行番号+move_gyou,列番号+move_retuがグリッドの中にあれば if 0<=gyou+move_gyou<N and 0<=retu+move_retu<N: # (行番号,列番号)→(行番号+move_gyou,列番号+move_retu) move_X[gyou+move_gyou][retu+move_retu]=X[gyou][retu] # 平行移動後のグリッドを返す return move_X # S,Tが一致しているか確認する関数 def check(S,T): # 行0~Nまで for gyou in range(N): # 列0~Nまで for retu in range(N): # もしS[gyou][retu]とT[gyou][retu]が一致しなければ if S[gyou][retu]!=T[gyou][retu]: # Falseを返して終了 return False # 完全に一致していればTrueを返す return True # 入力の受け取り N=int(input()) # S,Tを用意 S=[] T=[] # N行受け取り for i in range(N): # 文字列でSを受け取り S_tmp=input() # リストに変換 S_tmp=list(S_tmp) # Sへ格納 S.append(S_tmp) # N行受け取り for i in range(N): # 文字列でTを受け取り T_tmp=input() # リストに変換 T_tmp=list(T_tmp) # Tへ格納 T.append(T_tmp) # #の数が違う場合 if count_sharp(S)!=count_sharp(T): # Noを出力 print("No") # 終了 exit() # 4回回転する(90→180→270→360(0)) for i in range(4): # Sを回転 S=rotate(S) # S,Tの最初の#が出てくる行番号、列番号を確認 S_first_gyou,S_first_retu=first_sharp(S) T_first_gyou,T_first_retu=first_sharp(T) # 行平行移動量=Tの最初の#が出てくる行番号-Sの最初の#が出てくる行番号 move_gyou=T_first_gyou-S_first_gyou # 列平行移動量=Tの最初の#が出てくる列番号-Sの最初の#が出てくる列番号 move_retu=T_first_retu-S_first_retu # Sを下方向へmove_gyou,右方向へmove_retu平行移動 S_Trans=Translation(S,move_gyou,move_retu) # 一致しているかチェック if check(S_Trans,T)==True: # 一致していたらYesを出力 print("Yes") # 終了 exit() # 4回転試して一致しなければNoを出力 print("No") D - Rectangles 制約が4≤N≤2000なのでO(N^2)の解法でもTLEはなさそうです。 長方形というのは対角線上の点が決まれば他の点の場所が決まります。 例えば図の青い点p1,p2がきまればp3,p4の座標は定まります。 具体的には p1(p1_x,p1_y) p2(p2_x,p2_y) ならば p3(p1_x,p2_y) p4(p2_x,p1_y) となります。 このp3,p4に当たる点が存在するかをチェックすればOKです。 が、リストの中からp3,p4があるかチェックしていれば終わりません。 そこでdefaultdictを使いましょう。 使ったことがない人は以下を読んでください。 defaultdict(連想配列)について 辞書または連想配列と言います。 キー:値 というような対応付を行えるデータ構造です。(辞書という言い方もします) 連想配列はdict()と書くことでも使えますがデフォルトの値(初期値)が設定できません。そのため存在チェックなど色々面倒が発生します。 その面倒を避けるためにdefaultdictを使います。 import:from collections import defaultdict 作成(デフォルト0):変数名=defaultdict(int) キー、値の登録:変数名[キー]=値 値の取り出し:変数名[キー] 【使用例】 # インポート from collections import defaultdict # 作成(デフォルト0):変数名=defaultdict(int) dictionary=defaultdict(int) # キー、値の登録:変数名[キー]=値 dictionary[5]=1 # 値の取り出し:変数名[キー] x=dictionary[5] 詳しく知りたい人は以下を参照してください。 defaultdictはキーの部分にタプルを使用できます。 タプルはリストのようなもので、たとえば(1,3)をキーとした値を1、というような登録ができます。 具体的にはまずp_existというdictを用意します。 そしてx,yの受け取りと同時にp_exist[(x,y)]=1としておきます。 こうすることで座標が(x,y)の点が存在するかどうか⇔p_exist[(x,y)]=1か で確認できるというわけです。 あとは二重ループですべての点の組を確認します。点の組を(p1,p2)としたときにp3,p4にあたる点があるかどうか確認すればOKです。 ※p1,p2のx座標またはy座標が一致している場合は長方形を作りようがないのでスルーします。 ただしそれぞれの長方形について対角線の点のとり方は「左下」と「右上」、「左上」と「右下」の二通り存在するので二重にカウントしてしまいます。 そのため答えの出力前に割る2しておきましょう。 【提出】 # 入力を受け取り N=int(input()) # 座標の格納用リスト points=[] # defaultdictを用意 from collections import defaultdict p_exist=defaultdict(int) # M行受け取り for i in range(N): # x,yを受け取り x,y=map(int, input().split()) # 座標をpointsへ格納 points.append([x,y]) # dictに1を記録 p_exist[(x,y)]=1 # 答えを格納する変数 ans=0 # p1=0~Nまで for p1 in range(N): # p2=p1+1~Nまで for p2 in range(p1+1,N): # p1のx座標,y座標 p1_x,p1_y=points[p1] # p1のx座標,y座標 p2_x,p2_y=points[p2] # x座標またはy座標が同じ場合は if p1_x==p2_x or p1_y==p2_y: # 次の点へ continue # 座標(p1_x,p2_y)と座標(p2_x,p1_y)の点がそんざいすれば if p_exist[(p1_x,p2_y)]==1 and p_exist[(p2_x,p1_y)]==1: # 答えにプラス1 ans+=1 # 二重に数えているので2で割る ans//=2 # 答えを出力 print(ans) E - Destruction UnionFindというデータ構造を使います。 cが0以下の辺は取り除く意味がないということはすぐにわかります。 どの辺を取り除いてよいか?を考えると難しいので最初に辺を全て取っ払い、必要なものだけ安い順につなげましょう。 以下の手順で解きます。 (1)すべての辺を取り除き、報酬をすべて受け取ります(報酬がプラスのもののみ受け取ります) (2)報酬が低い順に辺を並び替えます (3)報酬が0以下の辺を全てつなぎます (4)報酬が0より大きい辺について  ・A,Bが連結なら無視します  ・A,Bが非連結なら報酬Cを返して(受け取った報酬からマイナスCして)辺をつなぎます (4)のA,Bが連結かどうか?の判定とA,Bの連結にUnionFindを使います。 UnionFindはグループ分けを高速でできるデータ構造です。 具体的には以下のことができます。 ・a,bを同じグループとする⇔a,bを連結する:O(logN)くらい(厳密には違いますがO(logN)くらいと思っておけばOKです) ・a,bが同じグループか判定する⇔a,bが連結か確認する:O(1)くらい(厳密には状況により違いますがO(1)くらいと思っておけばOKです) とにかくめちゃくちゃ速く上記2つができるデータ構造だと思えば良いです。 きちんとした説明はAtCoder公式が解説スライドを作っているのでそちらを御覧ください。 実装はかなり難しいです。ですが問題を解くのにそこまで理解する必要はなく、以下のUnionFindクラスをコピペして使えればOKです。 # UnionFindを用意 class UnionFind: def __init__(self,n): self.n=n self.parent_size=[-1]*n def leader(self,a): if self.parent_size[a]<0: return a self.parent_size[a]=self.leader(self.parent_size[a]) return self.parent_size[a] def merge(self,a,b): x,y=self.leader(a),self.leader(b) if x == y: return if abs(self.parent_size[x])<abs(self.parent_size[y]):x,y=y,x self.parent_size[x] += self.parent_size[y] self.parent_size[y]=x return def same(self,a,b): return self.leader(a) == self.leader(b) def size(self,a): return abs(self.parent_size[self.leader(a)]) def groups(self): result=[[] for _ in range(self.n)] for i in range(self.n): result[self.leader(i)].append(i) return [r for r in result if r != []] 上記をコードの最初にコピペしてから以下のように使います。 ・初期化:変数名=UnionFind(要素の数) ・根の確認:変数名.leader(要素番号) ・グループ化:変数名.merge(要素番号1,要素番号2) ・同一グループかの確認:変数名.same(要素番号1,要素番号2) ・所属するグループのサイズ確認:変数名.size(要素番号) ・グループ全体の確認:変数名.groups() 【初期化時の注意事項】 本問のように頂点の番号が1から始まる場合、 初期化:変数名=UnionFind(N+1) としなければならないことに注意してください。 このクラスは頂点番号が0インデックスでの使用を想定しているためです。 【使用例】 # 初期化:変数名=UnionFind(要素の数) UniFi=UnionFind(10) # グループ化:変数名.merge(要素番号1,要素番号2) UniFi.merge(0,2) UniFi.merge(1,3) UniFi.merge(3,0) # 根の確認:変数名.leader(要素番号) leader_x=UniFi.leader(1) # 同一グループかの確認:変数名.same(要素番号1,要素番号2) if UniFi.same(1,5): print("同一グループ") else: print("別グループ") # 所属するグループのサイズ確認:変数名.size(要素番号) size_x=UniFi.size(1) # グループ全体の確認:変数名.groups() print(UniFi.groups()) UnionFindのより詳細な説明、クラスの内容の解説については拙著『AtCoder 凡人が『緑』になるための精選50問詳細解説』に記載しています。 内容までしっかり理解しておきたいという人は購入をご検討ください。 詳細は本ページ下部【広告】を御覧ください。 この問題では ・a,bが連結かどうか?→same(a,b)がTrueか? ・a,bの連結→merge(a,b) とUnionFindを使うことで確認と連結を高速で行うことができ、TLEせずに解けます。 【提出】 # UnionFindを用意 class UnionFind: def __init__(self,n): self.n=n self.parent_size=[-1]*n def leader(self,a): if self.parent_size[a]<0: return a self.parent_size[a]=self.leader(self.parent_size[a]) return self.parent_size[a] def merge(self,a,b): x,y=self.leader(a),self.leader(b) if x == y: return if abs(self.parent_size[x])<abs(self.parent_size[y]):x,y=y,x self.parent_size[x] += self.parent_size[y] self.parent_size[y]=x return def same(self,a,b): return self.leader(a) == self.leader(b) def size(self,a): return abs(self.parent_size[self.leader(a)]) def groups(self): result=[[] for _ in range(self.n)] for i in range(self.n): result[self.leader(i)].append(i) return [r for r in result if r != []] # 入力の受け取り N,M=map(int, input().split()) # 辺の情報格納用リスト edge=[] # 答えの格納用変数 ans=0 # M行受け取り for i in range(M): A,B,C=map(int, input().split()) # 後のソートのためC先頭で格納 edge.append([C,A,B]) # Cがプラスなら if 0<C: # 答えにCをプラス ans+=C # 辺の報酬が低い順にソート edge.sort() # UnionFindを初期化(頂点数N+1個(0~N)) UniFi=UnionFind(N+1) # edgeの各要素について for c,a,b in edge: # もしcが0以下なら if c<=0: # つなぐ UniFi.merge(a,b) # そうでないならば(cが0より大きいなら) else: # もしa,bがまだつながっていない=連結でないならば if UniFi.same(a,b)==False: # cの報酬を返す ans-=c # a,bをつなぐ UniFi.merge(a,b) # 答えを出力 print(ans) 【広告】 「AtCoder 凡人が『緑』になるための精選50問詳細解説」 AtCoderで緑になるための典型50問をひくほど丁寧に解説した本(kindle)、pdf(booth)を販売しています。 値段:100円(Kindle Unlimited対象) kindle:https://www.amazon.co.jp/gp/product/B09C3TPQYV/ booth(pdf):https://sano192.booth.pm/items/3179185 サンプルは以下から。 https://qiita.com/sano192/items/eb2c9cbee6ec4dc79aaf
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ノイズ入り波形の生成

ポイント ➀波形データは時系列解析などで重要となってくる。ノイズ生成やノイズ除去の方法について整理しておく必要がある。 ➁波形データの特徴量 ・振幅(基線からの波の振れ幅) ・周期(一回振動するのにかかる時間) ・振動数(一秒間あたりに振動する回数) ・波長(一回振動するときのx軸方向の長さ) ➂sin波のy座標に対して乱数値を足し合わせることによって、ノイズを表現することができる。 ➃今回のグラフ表示には、matplotlibを使用した。このライブラリの機能の特徴としては、 ・グラフを重ね合わせて表示することができる。 ・表示範囲について、X軸もしくはY軸方向の最大値・最小値を設定することができる。 ・表示する曲線の色や点線の有無などの設定をすることができる。 ・ラベル、Grid線、凡例(Legend)の表示を行うことができる。 ・横線や縦線を引くことができる。 ソース import numpy as np import matplotlib.pyplot as plt n=100 #x軸方向のデータ数 xmin, xmax = 0,19 #x軸方向の範囲(0~6π)にかけて、100個のx座標データを用意する。 x = np.linspace(0, 6*np.pi, n) #きれいなsin波とノイズ入りsin波の準備 y=np.sin(x) y_noise= y + 0.2 * np.random.randn(n) #波形の描写 p = plt.plot(x, y, "blue", linestyle='solid') p = plt.plot(x, y_noise, "red", linestyle='solid') p = plt.hlines([0], xmin, xmax, "yellow", linestyles='dashed') # 基線 p = plt.xlim(xmin, xmax) p = plt.ylim(-1.5, 1.5) p = plt.xlabel('X') p = plt.ylabel('Y') plt.show(p) 出力
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む