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

ゼロから始めるLeetCode Day66「438. Find All Anagrams in a String」

概要

海外ではエンジニアの面接においてコーディングテストというものが行われるらしく、多くの場合、特定の関数やクラスをお題に沿って実装するという物がメインである。

どうやら多くのエンジニアはその対策としてLeetCodeなるサイトで対策を行うようだ。

早い話が本場でも行われているようなコーディングテストに耐えうるようなアルゴリズム力を鍛えるサイトであり、海外のテックカンパニーでのキャリアを積みたい方にとっては避けては通れない道である。

と、仰々しく書いてみましたが、私は今のところそういった面接を受ける予定はありません。

ただ、ITエンジニアとして人並みのアルゴリズム力くらいは持っておいた方がいいだろうということで不定期に問題を解いてその時に考えたやり方をメモ的に書いていこうかと思います。

Leetcode

Python3で解いています。

ゼロから始めるLeetCode 目次

前回
ゼロから始めるLeetCode Day65「560. Subarray Sum Equals K」

今はTop 100 Liked QuestionsのMediumを優先的に解いています。
Easyは全て解いたので気になる方は目次の方へどうぞ。

Twitterやってます。

技術ブログ始めました!!
技術はLeetCode、Django、Nuxt、あたりについて書くと思います。こちらの方が更新は早いので、よければブクマよろしくお願いいたします!

問題

438. Find All Anagrams in a String

難易度はMedium。
Top 100 Liked Questionsからの抜粋です。

文字列sと空でない文字列pが与えられたとき、sの中のpのアナグラムの開始インデックスをすべて求めなさい、という問題です。

Stringsは英小文字のみで構成され、spの長さはともに20,100を超えてはいけません。

なお、出力の順番は関係ありません。

Example 1:

Input:
s: "cbaebabacd" p: "abc"

Output:
[0, 6]

Explanation:
The substring with start index = 0 is "cba", which is an anagram of "abc".
The substring with start index = 6 is "bac", which is an anagram of "abc".

Example 2:

Input:
s: "abab" p: "ab"

Output:
[0, 1, 2]

Explanation:
The substring with start index = 0 is "ab", which is an anagram of "ab".
The substring with start index = 1 is "ba", which is an anagram of "ab".
The substring with start index = 2 is "ab", which is an anagram of "ab".

解法

アナグラムの開始のインデックスを整理する必要があります。
解法としてはSliding Windowというアルゴリズムが有名なようで、別に記事を書く予定です。
ネットワークでは聞いたことのあるワードですがアルゴリズムで聞いたことはなかったですね。

ただ、ロジックを真似て書いてみたものが以下のように

import collections
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        len_s,len_p,set_p,ans = len(s),len(p),Counter(p),[]
        for i in range(len_s-len_p+1):
            if Counter(s[i:i+len_p]) == set_p:
                ans.append(i)
        return ans
# Runtime: 7868 ms, faster than 7.31% of Python3 online submissions for Find All Anagrams in a String.
# Memory Usage: 14.8 MB, less than 69.94% of Python3 online submissions for Find All Anagrams in a String.

めちゃくちゃ遅くて、これはどうしたもんかと思いました・・・

とりあえずdiscussを見て理解してみようと試みました。

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        LS, LP, S, P, A = len(s), len(p), 0, 0, []
        if LP > LS: return []
        for i in range(LP): S, P = S + hash(s[i]), P + hash(p[i])
        if S == P: A.append(0)
        for i in range(LP, LS):
            S += hash(s[i]) - hash(s[i-LP])
            if S == P: A.append(i-LP+1)
        return A

するとこんな解答が。
コードを見てみると、counterを使う代わりにハッシュマップを使っていて、残りは大きく変わることはなさそうです。

とりあえずスピードを見てみると・・・

Runtime: 68 ms, faster than 99.89% of Python3 online submissions for Find All Anagrams in a String.
Memory Usage: 14.9 MB, less than 42.94% of Python3 online submissions for Find All Anagrams in a String.

!?

とんでもなく、速いですね・・・
確かに考えてみればCounterを要素の度に回すと明らかに効率悪いですよね・・・
for文で回す時にその度に要素を全て回してdicで返すということは、要素が多ければ多いほど飛躍的に遅くなっていくというかなり悪い実装方法です。

しかしHashMapに入れるという発想はなかったですね・・
discussを見てるとこういう発想に出会えるのでしっかりみることが重要ですね。

では今回はここまで。
お疲れ様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tkinterで手動で更新するライフゲームをつくる

PaizaのPython講座を受けて、さて、何を作ろうとgoogleで探していたら、マイナビニュースの【ゼロからはじめるPython】にライフゲームの作り方(https://news.mynavi.jp/article/zeropython-9/) があったのでそれを参考に、手動で更新するライフゲームを作ってみた。

実行すると、下記の黒い画面が出て、
image.png

クリックするとセルを配置、取り消しができる。
下記は【ペンタデカスロン】という配置
image.png

[Enter]キーを押すごとに更新する。
image.png

【ペンタデカスロン】という配置は一定周期で繰り返し、滅びることはないが、
途中で1つのセルを加えることで、
image.png
バランスが崩れ、新しい形ができる。
image.png

import tkinter as tk

WIDTH, HEIGHT = 600, 400  # Canvasの大きさ
cell = {'size': 20, 'color': 'green'}  # セルの情報
COLS, ROWS = WIDTH//cell['size'], HEIGHT//cell['size']  # Canvasの列数、行数
data = [[False for x in range(COLS)] for y in range(ROWS)]  # セルの存在

win = tk.Tk()
win.title('LifeGame')

# 世代の更新回数
text = tk.StringVar()  # 文字列を保持するWidget変数
update = 0  # 更新回数の初期値
text.set(update)  # 更新回数を設定
label = tk.Label(win, textvariable=text,  font=('IPAexゴシック', '24'))
label.pack()

# Canvasの設定
cv = tk.Canvas(win, width=WIDTH, height=HEIGHT, bg='black')
cv.pack()


# セルを配置しやすいように格子状に線を描画する
def draw_grid():
    for x in range(COLS):
        x1 = x * cell['size']
        cv.create_line(x1, 0, x1, HEIGHT, fill='white')
    for y in range(ROWS):
        y1 = y * cell['size']
        cv.create_line(0, y1, WIDTH, y1, fill='white')


# マウスのクリックでセルを配置したり、取り消したりする
def place_cells(e):
    draw_grid()
    # クリックした座標から、セルを描画するindexを計算する
    x, y = e.x // cell['size'], e.y // cell['size']
    # 既に描画しているセルを削除する。
    if data[y][x]:
        cv.delete('current')
        data[y][x] = False
    # セルを描画する。
    else:
        x1, y1 = x * cell['size'], y * cell['size']
        cv.create_oval(x1, y1, x1+cell['size'], y1+cell['size'], fill=cell['color'])
        data[y][x] = True


cv.bind('<Button>', place_cells)  # Canvasがクリックされたら


# 周りのセルがいくつ生きているかによって次世代の自分の運命が決まる
def check_cells(x, y):
    # 周りのセルの相対座標
    tbl = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]
    cnt = 0  # 周りのセルがいくつ生きているかの個数
    # 周りのセルを順々にチェックする
    for t in tbl:
        xx, yy = x + t[0], y + t[1]  # 周りのセルの絶対値を計算する。
        if not 0 <= xx < COLS or not 0 <= yy < ROWS:
            continue
        if data[yy][xx]:
            cnt += 1  # 周りのセルが生きていればカウントする。

    if cnt == 3:
        return True  # 誕生
    if data[y][x]:  # 自分が生きているなら
        if 2 <= cnt <= 3:
            return True  # 生存
        return False  # 過密、過疎
    return data[y][x]  # 現状維持


# セルの次世代を運命を調べる。
def next_turn(e):
    global data
    global update
    # 更新したデータを1時保存する
    data2 = [[check_cells(x, y) for x in range(COLS)] for y in range(ROWS)]
    data = data2  # データを更新
    update += 1  # 更新回数をカウントする
    text.set(update)  # 世代の回数を更新
    draw()


win.bind('<Return>', next_turn)  # Enterが押されたら


# セルを描画する。
def draw():
    cv.delete('all')
    for y in range(ROWS):
        for x in range(COLS):
            if data[y][x]:
                x1, y1 = x * cell['size'], y * cell['size']
                cv.create_oval(x1, y1, x1+cell['size'], y1+cell['size'], fill=cell['color'])


win.mainloop()

ライフゲームは色々なパターンがあるらしい。https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでLine通知やってみた

pythonを用いてLineNotifyを送信するプログラムを作ったので、載せてみます。
これから通知を作りたい人の参考になれば幸いです。

目次

コードを読まなくてもいいよって方は4から見ても大丈夫です
1.背景
2.やったこと
3.コード
4.使い方
5.まとめ

背景

  • 機械学習の学習時間が非常に長い...
  • 学習させている間に他のことをしていたい
  • 学習が終了したら教えてほしい

やったこと

  • 他のファイルからimportして使えるようにした
  • LineNotifyを用いて自分にLineメッセージを送れるようにした

コード

もっとシンプルに作っても良かったのですが、今後も使っていく事を考えてモジュール化しました。
コードはGithubに載せておきます。
requests がインストールされていないと使えないのでしてない方はインストールしてください。

>> pip install requests

実際のコード↓
(簡単な処理しか書いていないので説明は省略します。)

line_notify.py
"""
class module
LINE Notify を使うクラスを定義
"""
# coding: utf-8
import requests


class LineNotify():
    """
    LINE通知を行うクラス
    """

    def __init__(self):
        """
        Parameters
        ----------
        url: string
            LineNotifyの送り先
        access_token: string
            LineNotifyのアクセストークン
        """
        self.url = "https://notify-api.line.me/api/notify"
        self.access_token = 'F78NKUuw6BhAytCPpjw2QgXXMmxV58iOxBA0HtQfYzh'

    def post_linenotify(self, message):
        """
        Lineにメッセージを送る

        Parameters
        ----------
        self : self
            クラス自身
        message: string
            実際に送るメッセージ
        """
        headers = {'Authorization': 'Bearer ' + self.access_token}
        payload = {'message': message}
        requests.post(self.url, headers=headers, params=payload,)

使い方

LineNotifyの公式からアクセストークンを取得してくる。

右上の自分の名前をクリックしマイページへ行く。
スクリーンショット 2020-06-24 22.30.28.png

マイページ下部のトークンを発行するをクリックする。
スクリーンショット 2020-06-24 22.31.18.png

送りたいグループを選択して発行されたトークンをコピーする
スクリーンショット 2020-06-24 22.32.01.png

Githubからクローンする

クローンした後にアクセストークンを設定する

self.access_token = 'ここに取得したトークンを入れる'

実際にメッセージを送信するコードを書く

main.py
from line_notify import LineNotify


def main:
    notify = LineNotify()
    message = '送信するメッセージ'
    notify.post_linenotify(message)


if __name__ == '__main__':
    main()

こんな感じで使えると思います。

まとめ

  • LineNotifyをpythonを用いて送信してみた
  • コードのモジュール化

もし、使えそうな方がいれば使ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テキストマイニングのサンプルデータを自力で準備する

概要

最近テキストマイニングの勉強を始めたのだが、いい感じのサンプルデータが手に入らず苦労した。自分と同じ悩みを抱えている人もいるかもしれないので、自力でサンプルデータを準備するまでの試行錯誤を記事にする。

なお、私は職場でアンケートの自由記述(感想とか要望とか)を分析することが多いので、なるべく似た形式のデータを手に入れるのが目標。

手段の検討

青空文庫

テキストマイニングの本でもよく見かける青空文庫は、著作権の消滅した文学作品などを掲載したWebサイト。ただ、アンケートデータには似ていないので今回は見送り。

  • メリット
    • 簡単にかなりの分量のテキストが手に入る。
    • 読んだことのある作品は、事前知識もあって分析しやすい。
  • デメリット
    • アンケートデータとは形式が全く異なる。

スクレイピング

例えばOpenWork転職会議のような会社の評価が集まるサイトをスクレイピングすることを考える。非常におもしろそうだが、今回はデメリットが大きいと判断して見送り。

  • メリット
    • 自分の会社に限定すれば、事前知識もあって分析しやすい。
    • 中途or新卒採用のような回答者の属性も充実しており、深堀りしやすい。
  • デメリット
    • 記事の確認にログインが必要など、若干スクレイピングの難易度が高い。
    • サイトの利用規約に違反していたら、この記事を書いた自分が叩かれそう。

TwitterのAPI

企業が行っている感想投稿キャンペーンを利用すれば、アンケートの自由記述に似たデータが手に入りそう(例えばスターウォーズとかでもやっていた)。ただ、実際に見てみるとTwitterのクセがかなり強いのでこれも見送り。

  • メリット
    • APIが整備されているため、利用しやすい。
  • デメリット
    • 画像ありきの投稿もあったり、URLやハッシュタグが多かったりと、Twitter独自のクセが強い。

ECサイトのAPI

特定の商品のレビューを抽出できたら、アンケートの自由記述に似たデータが手に入りそう。Amazon、楽天市場、Yahoo!ショッピングなどいろいろあるが、Yahoo!ショッピングの商品レビュー検索APIならJANコード1を指定してレビューを取得できそう!明確なデメリットも思い浮かばないので、今回はこのAPIを利用します

  • メリット
    • APIが整備されているため、利用しやすい。
    • 興味のある商品のレビューに限定すれば、事前知識もあって分析しやすい。

Yahoo!ショッピングWeb APIを叩く

アプリケーションIDの取得

ここを参考に、アプリケーションIDを取得する。この記事を再現するだけなら、設定は以下のままいじらなくてよさそう(サイトURLもhttp://example.com/のまま登録できた)。
image.png

レビューの取得

ここからはPythonで実行する。appidは先ほど取得したアプリケーションIDに置き換えること(管理画面ではClient Idと記載されているかもしれない)。以下ではとりあえず商品の評価(rate)とレビューの本文(description)のみ取得しているが、その気になればもっといろいろな情報がとれる(詳細は公式ドキュメントを確認)。一度に50件までしか取得できないが、それ以上必要な場合もstartを50ずつずらして繰り返せばいけそう。

import requests
import json
import pandas as pd
url = "https://shopping.yahooapis.jp/ShoppingWebService/V1/json/reviewSearch"
payload = {
    "appid": "XXXXXXXXXX",
    "jan": "4902777323176", # ザバスのプロテイン
    "results": 50, # default... 10, max... 50
    # "start": 1
}
res = json.loads(requests.get(url, params=payload).text)["ResultSet"]["Result"]
rate = [x["Ratings"]["Rate"] for x in res if x["Description"] != ""] # 評価
description = [x["Description"] for x in res if x["Description"] != ""] # レビュー
df = pd.DataFrame({
    "rate": rate,
    "description": description,
})
df.to_csv("review.csv", header=True, index=False)

確認

上のコードを実行すると、review.csvというファイルができるので、後は好きな方法で分析できる。とりあえずスプレッドシートで開いて確認するとこんな感じ。意外と商品そのものより、配送についてのレビューが多い印象。高評価・低評価のレビューを比較して分析するのもおもしろそう。
image.png

これでやっとテキストマイニングの勉強ができる...


  1. とりあえずはバーコードの番号だと思って差し支えないはず。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gunicornの再起動方法

Tips

gunicornの再起動方法

方法

% ps aux | grep gunicorn

表示されるPIDの一番小さいプロセスIDがmasterプロセスのため、killを行う。

% kill -HUP <一番小さいプロセスID>

HUPオプションを付加することにより、単純にプロセスをkillするのではなく、プロセスを再起動(reload)させることができます。

Reference

https://docs.gunicorn.org/en/stable/faq.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

numpyのライブラリ関数一覧を少しずつ入れていく - b編

pythonでnumpyを使いだしていますが便利な関数が多数あり、こんなこともnumpy使えばできていたと気づくことが何度かあるので、numpyでできることを網羅するためにブログを書く勉強も兼ねてnumpy関数の一覧を作っています。今回はb編です。

 np.bartlett 0-1-0の山の数列をつくる。()内は要素数
  https://numpy.org/doc/1.16/reference/generated/numpy.bartlett.html
 np.base_repr (number, base=2, padding=0)進数を変換。baseの値が10なら10進数に
 https://numpy.org/doc/stable/reference/generated/numpy.base_repr.html
 np.binary_repr 数値を2進数のバイナリに変換
  リンク
 np.bincount array要素内の最頻値を出す
  https://qiita.com/nirs_kd56/items/08f087dfa89b77d3c897
 np.bitwise_and bitごとのand
  https://note.nkmk.me/python-numpy-logical-bitwise-and-or/
 np.bitwise_not bitごとの反転
 np.bitwise_or bitごとのor
 np.bitwise_xor bitごとのxor
 np.blackman ブラックマン窓  窓関数の一つで周波数解析に使う
  http://aidiary.hatenablog.com/entry/20110716/1310824587
 np.block arrayを配置を指定して結合する
  https://note.nkmk.me/python-numpy-concatenate-stack-block/
 np.bmat ([],[])で入れてグリッド状に結合する
 np.bool 真偽値の型
 np.bool8 8ビットのbool型
 np.bool_ np.boolに類似。型の細かいところが違うと思うが?下をチェック。
  https://ja.coder.work/so/python/169925
np.bool8 8ビットのbool型
 np.bool_ np.boolに類似。型の細かいところが違うと思うが?下をチェック。
  https://ja.coder.work/so/python/169925
 np.broadcast 要素数が足りないときに自動で拡張してくれる機能をブロードキャストという。この関数はブロードキャストを通した定義時に使う。
  https://qiita.com/capybara/items/2d5f387996a5bd275fbf
  https://stackoverrun.com/ja/q/9994569
 np.broadcast_arrays 複数の配列をブロードキャストして配列を揃える
  https://note.nkmk.me/python-numpy-broadcasting/
  np.broadcast_to 任意の形状にブロードキャストする
  https://note.nkmk.me/python-numpy-broadcasting/
 np.busday_count 週末をを無視して日数を数える。文字列で入力しても良い
  リンク
 np.busday_offset 週末を無視した営業日として日数を前に進める
  https://numpy.org/doc/stable/reference/generated/numpy.busday_offset.html
 np.busdaycalendar 祝日を定義する
  https://numpy.org/doc/stable/reference/generated/numpy.busdaycalendar.html
 np.byte バイト型
 np.byte_bounds  配列のポインタを返す。使用例としては
  http://www.366service.com/jp/qa/cf1dcfcf28c7009fb76ff3f6c8fbfe08
 np.bytes0  ?
 np.bytes_ ?  _がついている時の違いがわかれば理解できそう。

ところどころ本質部分が理解できずカバー不十分なところもあります。何かわかったら適宜更新して行く予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの可変長代入?

Pythonの代入文は、以下のように書けます。

a, b = 1, 2

また、以下のように書くことにより、可変長引数を持つ関数が書けます。

def func(*args):
  print(repr(args))

func(1)
# 出力: (1,)
func(1, 2)
# 出力: (1, 2)

まぁ、一応自分もPythonを始めてほどほど経つので、これくらいの事は当然知っているわけですが、ふと、代入文も「*」(アスタリスク)を使えないのだろうか、と疑問に思った次第です。

a, *b = 1, 2, 3
print((a, b))
# 出力: (1, [2, 3])

できるじゃん!

念のため、仕様がどのようになっているか、確認してみると、

"星付き"のターゲットと呼ばれる、頭にアスタリスクが一つ付いたターゲットがターゲットリストに一つだけ含まれている場合: オブジェクトはイテラブルで、少なくともターゲットリストのターゲットの数よりも一つ少ない要素を持たなければはなりません。 星付きのターゲットより前のターゲットに、イテラブルの先頭の要素が左から右へ代入されます。 星付きのターゲットより後ろのターゲットに、イテラブルの末尾の要素が代入されます。 星付きのターゲットに、イテラブルの残った要素のリストが代入されます (リスト空でもかまいません)。

しっかり書かれていますね。

いろいろとやってみましょう。

a, *b = 1,
print((a, b))
# 出力: (1, [])
a, *b, c = 1, 2, 3, 4, 5
print((a, b, c))
# 出力: (1, [2, 3, 4], 5)
*a, = 1, 2, 3
print(a)
# 出力: [1, 2, 3]

ですが、以下のようなものはダメです。

a, b, *c = 1,
# ValueError: not enough values to unpack (expected at least 2, got 1)
# cは空でもいいが、a,bに代入すべき値は必ず指定しなければならない。
*a = 1, 2, 3
# SyntaxError: starred assignment target must be in a list or tuple
# 「*」のつくターゲットは、リスト・タプルの中になければならない。

今までは左辺だけでしたが、右辺にも使えるようです。

d = 10, 20
a, b, c = 1, *d
print((a, b, c))
# 出力: (1, 10, 20)
# 「a, b, c = 1, d[0], d[1]」と同様。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

制御フローを読みやすく

制御フローを読みやすく

Readableコードを読んだので
制御フローを読みやすくする方法を書いてみる

条件式の引数の並び

if(length >= 10)
if(10 <= length)

どちらも同じ意味を持っているけど最初の方が読みやすい

変化する値は左辺へ、変化しない値は右辺へ

先ほどの例だと10という定数を右辺へ、
lengthという変数を左辺へ持ってくると読みやすくなった。

日本語でも
「もし私が20才以上なら」
「20才が私の年齢以下なら」
を考えた時、前者の方がわかりやすい。

私の年齢は変化をしていくが20才は定数だ。
変化する値は左辺へ、変化しない値は右辺へは自然言語と同じ順にすることで
理解を容易にできることがわかる。

三項演算子

三項演算子はとても便利。
以下のように使うと助長なif-else文を短くできる

if(hour >= 12) {
  time_str += "pm";
} else {
  time_str += "am";
}

//三項演算子を使う
time_str += (hour >= 12) ? "pm" : "am";

でもやみくもに使ってしまうとわかりにくくなってしまう場合もある

return exponent >= 0 ? mantissa * (1<<exponent) : mantissa/(1<< -exponent);

ネストを浅くする

ユーザーの結果が成功、許可が得られたときは返信をする。
ユーザーの結果が成功、許可が出なかった時は空のエラーを返す。
ユーザーの結果が失敗だった時は、エラーを返す。という処理。

ちょっと複雑になってしまっている。

if(user_result == SUCCESS) {
  if(permission_result != SUCCESS) {
    reply.WriteErrors("error reading permissions");
    reply.Done();
    return;
  }
  reply.WriteErrors("");
} else {
  reply.WriteErrors(user_result);
}
reply.Done();

早めに返してネストを削除する

//先にuser_result != SUCCESSの結果を返してしまう。
if (user_result != SUCCESS) {
  reply.WriteErrors(user_result);
  reply.Done();
  return;
}

//残りはuser_result == SUCCESSの場合のみとなる
if(permission_result != SUCCESS) {
  reply.WriteErrors(permission_result);
  reply.Done();
  return;
}

reply.WriteErrors("");
reply.Done();


これでネストを浅くすることができた。
returnで早めに返すことで制御フローを簡単にすることができた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python・AutoML】pythonでアルゴリズム比較のプロセスを楽にした話【AutoMLとpythonで分析、どう違う】

注:所感・ポエム要素もアリ

モデリ作りって面倒くさい

以下はkaggle daysで使われた「最終的なモデルにたどり着くまでの試行錯誤」を表した一枚のスライドですが、
機械学習モデルを作る過程って前処理を除いてもこんな手数がかかるものだったりします。
(実社会の整備していないデータからモデル,実装を考えると中々頭が痛い。)

ただ実問題はkaggleほどの厳密さを求められなかったり、ひとまず導入・ひとまず数%の改善でもよかったりするざっくりとした案件(一面)もあります。

作業自動化したいなぁ

モデル作りも慣れてくると同じようなEDA(探査的データ解析)と同じようなモデリング(ベースライン→研磨)になってきますので、
これを簡単にしたいと思い、ある程度自由に変更できる簡略スクリプトを作ってみました。

まず図だけで説明し、スクリプトは後半にまとめてます。

モデル作りのプロセス

image.png

  • EDA → cleansing
    • まずデータを見て、可視化して、NAやNULL処理、ID列を消す、などの処理をしてから予測に使えるよう成形します。
    • 実問題ではこの前にDataBaseにつないでjoinして整合性を確かめて個人情報を消す処理などが入ります。
  • EDA → model(base line)
    • ある程度知識がある人ならばデータと課題から使えそうなモデル候補が頭に浮かびます
    • いくつかのモデルを比較して、どのモデルが一番良いかを決めるため、ある程度デフォルト設定値で問題を解かせてみます
    • これをbaselineのモデルと言います
    • 各アルゴリズムのbaselineを比較しながらどのアルゴリズムで行くか決めます
  • model → training
    • 候補として取り上げたモデルに絞り、パラメータのtuningや交差検証などを行い精度を上げます
  • training → predict
    • 学習が終わったモデルを使って実際に予測に使います。
  • ダメなら別のアルゴリズムからアプローチをします
  • そもそも抽出したデータが間違っていたり、タスクの設定のし直しがあれば振り出しに戻ります

水色部分を簡略化した

基本的な可視化とアルゴリズム比較だけ行って、課題解決までの手間を簡略化します。

1.最小限の全体俯瞰を行って

image.png

01.jpg

02.jpg

2.ダミー化・列削除・testsplitして

3.デフォルト・パラメータ変更したモデルでbaselineを決めます
(LGBMがClassifierになってますがregressionの問題です)
image.png

使い慣れたパッケージを自分好みにカスタムしながら並列で試せるスクリプトを作ってみました。
というのもモデルをリストにしてfor文で回しているだけなんですが、一つ一つ使うよりも体系的にまとめたスクリプトを組んだほうが楽だろうなと安易な発想で書いたわけです。(以下抜粋)

models = [
    SGDRegressor(max_iter=1000, tol=1e-3),
    Lasso(alpha=0.1),
    ElasticNet(random_state=0),
    Ridge(alpha=.5),
    SVR(gamma='auto', kernel='linear'),
    SVR(gamma='auto', kernel='rbf'),
    BaggingRegressor(),
    BaggingRegressor(KNeighborsClassifier(), max_samples=0.5, max_features=0.5),
    NuSVR(gamma='auto'),
    RandomForestRegressor(random_state=0, n_estimators=300),
    xgb.XGBRegressor(),
    LinearRegression(),
    lgb.LGBMRegressor(num_leaves=80)
]

for model in models:
    model.fit(x_train, y_train) 
    y_res = model.predict(x_val)
    mse = mean_squared_error(y_val, y_res)
    score = model.score(x_val, y_val)    
    table.add_row([type(model).__name__, format(mse, '.2f'), format(score, '.2f')])

print(table)

それ以降はチューニングと予測になるわけですが、今回のモデル比較ではRandom Forestが一番いい値なのでRFから掘り進めていくことになると思います。
一応予測パートもスクリプトとしてつけておきましたので興味があれば一番下を参照してください。

pycaretの紹介

似たような方法としてもっと楽なのはpycaretというライブラリがあり、これはもっと簡単に実行できます。

image.png

実行記事

ただ、やはり自分好みのカスタムコーディング要素を残しつつ、自動化していく方法として今回記事を書きました。

AutoMLとどう違うのか

たとえば今回の手順のような

03.png

がチェックボックスを弄って進めることで

04.png

のように簡単になるものです。
出典:DataRobot
データのクレンジング、変数の選択も選べますし、ダミー化したい変数を選択しておくと内部で何百パターンの使えそうな変数を作って比較してくれたりもします。

今回のように複数のアルゴリズムを並列に走らせて比較、チューニングするという機能もあります。

使いこなせたら便利そうですね。
(試用できるAutoMLも過去記事で紹介しているので興味があれば。)

AutoMLの話が挙がるとよく出る誤解と所感

???「コーディング不要だから非エンジニアでも使えて、導入できればデータサイエンティストは不要」
???「作ったモデルがすぐ実運用に使えるから外注コスト削減になる」
???「データさえ入れたらいい感じの答えが出てくる」

一番言ってほしくない偉い人たちに限って誤解してたりします。
そういう場合にはまだまだ議論や現状整理が足りて無いので、データサイエンティスト不要とか言わずにデータがわかる事業整理・業務改革コンサルでも入れるべきです。

AutoML以前に現状整理すべきことで出てくる問題は

  • MLで解決したい案件がそれだけあるのか (財布買っても中身は空状態)
  • データの質・整形について議論できるか (有効な変数やテーブルが結合できているのか)
  • 検証と評価が正しく出来るか (決定係数やMSEで比較するのって適しているのか、検証セットに標本何割割いていいか)
  • 抽出データ・学習の粒度が目的に合っているのか (一か月先を知りたいのに一日先しか予測できないモデルになっている)
  • データ収集,DXから入らなければならない状態ではないのか (そもそも使えるレベルのデータでない)
  • 保守・運用できるのか (各自各部署勝手に作り出して技術的負債・BlackBox化,RPA出始めでも起こる)
  • そもそもAIを魔法の杖と思ってないか (AutoMLは魔法の杖が無限に出てくる四次元ポケットではない)
  • などなど...

上記が問題なく議論されていれば、後は懐事情との相談で導入するか判断したらいいと思います。
ML課題がデータサイエンティストに対して過剰で、AutoMLの中のライブラリが解決方法につながっている場合はバッチリ活用できると思います。
間違いなく今、Pythonを使っているようなエンジニアの助けになります。

参照データ・記事

UCI
kaggle

この記事もkaggleのカーネルで学んでいる時に作り始めたのですが、刺激を受けたカーネルがわからず参照に乗せられていません。
わかり次第追加します。

以上

以下コード

github repositories

#import
import pandas as pd
from sklearn.model_selection import RandomizedSearchCV
from sklearn.metrics import mean_squared_error, mean_squared_log_error
from sklearn.linear_model import Lasso, ElasticNet, Ridge, SGDRegressor
from sklearn.svm import SVR, NuSVR
from sklearn.ensemble import BaggingRegressor, RandomForestRegressor
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

import xgboost as xgb
from sklearn.model_selection import GridSearchCV
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

from prettytable import PrettyTable

import lightgbm as lgb
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score


#ダミー化関数の定義
def generate_dummies(df, dummy_column):
    dummies = pd.get_dummies(df[dummy_column], prefix=dummy_column)
    df = pd.concat([df, dummies], axis=1)
    return df

#データ読み込み
train = pd.read_csv('C:/Users/train.tsv',sep='\t')
test  = pd.read_csv('C:/Users/test.tsv',sep='\t')
train.head()


#特徴量の名前や欠損を調べる
train.info()


#データの特徴量が量的か質的か判断、使わないものも選んでおく

category_features = ['season','yr',  'mnth', 'hr','holiday', 'weekday', 'workingday', 'weathersit']
number_features = ['temp', 'atemp', 'hum', 'windspeed']
remove_features = ['atemp','id','dteday','yr']
target = ['cnt']
features= category_features + number_features

#最低限の可視化 hist
train.hist(figsize=(12,10))


#最低限の可視化 相関

matrix = train[number_features + target].corr()
heat = np.array(matrix)
heat[np.tril_indices_from(heat)] = False
fig,ax= plt.subplots()
fig.set_size_inches(20,10)
sns.heatmap(matrix, mask=heat,vmax=1.0, vmin=0.0, square=True,annot=True, cmap="Reds")

#test train splitと変数削除
X = pd.DataFrame.copy(train)
y = X[target]

for dummy_column in category_features:
    X = generate_dummies(X, dummy_column)

X = X.drop(category_features+target+remove_features, axis=1)

x_train, x_val, y_train, y_val = train_test_split(X, y, random_state = 22, test_size = 0.2)

#アルゴリズムの比較用テーブル

table = PrettyTable()
table.field_names = ["Model", "MSE", "R-sq"]

#アルゴリズムの比較

models = [
    SGDRegressor(max_iter=1000, tol=1e-3),
    Lasso(alpha=0.1),
    ElasticNet(random_state=0),
    Ridge(alpha=.5),
    SVR(gamma='auto', kernel='linear'),
    SVR(gamma='auto', kernel='rbf'),
    BaggingRegressor(),
    BaggingRegressor(KNeighborsClassifier(), max_samples=0.5, max_features=0.5),
    NuSVR(gamma='auto'),
    RandomForestRegressor(random_state=0, n_estimators=300),
    xgb.XGBRegressor(),
    LinearRegression(),
    lgb.LGBMRegressor(num_leaves=80)
]

for model in models:
    model.fit(x_train, y_train) 
    y_res = model.predict(x_val)
    mse = mean_squared_error(y_val, y_res)
    score = model.score(x_val, y_val)    
    table.add_row([type(model).__name__, format(mse, '.2f'), format(score, '.2f')])

print(table)

#一番良かったモデルを検証データで精度確認

table = PrettyTable()
table.field_names = ["Model", "Dataset", "MSE", 'RMSLE', "R-sq"]


model = RandomForestRegressor( random_state=0, n_estimators=100)
model.fit(x_train, y_train) 

def evaluate(x, y, dataset):
    pred = model.predict(x)
    mse = mean_squared_error(y, pred)
    score = model.score(x, y)    
    rmsle = np.sqrt(mean_squared_log_error(y, pred))
    table.add_row([type(model).__name__, dataset, format(mse, '.2f'), format(rmsle, '.2f'), format(score, '.2f')])

evaluate(x_train, y_train, 'training')
evaluate(x_val, y_val, 'validation')

print(table)

#パラメータ探索を定義
n_estimators = [int(x) for x in np.linspace(start = 200, stop = 2000, num = 10)]
max_features = ['auto', 'sqrt']
max_depth = [int(x) for x in np.linspace(10, 110, num = 11)]
max_depth.append(None)
min_samples_split = [2, 5, 10]
min_samples_leaf = [1, 2, 4]
bootstrap = [True, False]

random_grid = {'n_estimators': n_estimators,
               'max_features': max_features,
               'max_depth': max_depth,
               'min_samples_split': min_samples_split,
               'min_samples_leaf': min_samples_leaf,
               'bootstrap': bootstrap}
print(random_grid)

#探索実行

rf = RandomForestRegressor()
model = RandomizedSearchCV(estimator = rf, param_distributions = random_grid, n_iter = 100, cv = 3, verbose=2, random_state=0)
model.fit(x_train, y_train)
print(model)

#一番良かったパラメータを確認
model.best_params_

#改めて表にする
table = PrettyTable()
table.field_names = ["Model", "Dataset", "MSE", 'RMSLE', "R-sq"]
evaluate(x_train, y_train, 'training')
evaluate(x_val, y_val, 'validation')
print(table)

#重要度の計算
importances = rf.feature_importances_
std = np.std([tree.feature_importances_ for tree in rf.estimators_], axis=0)
indices = np.argsort(importances)[::-1]

dummy_features = x_train.columns.values
for f in range(dummy_features.shape[0]):
    print("%d. feature %s (%f)" % (f + 1, dummy_features[indices[f]], importances[indices[f]]))

#重要度の作図
# Plot the feature importances of the forest
plt.figure(figsize=(18,5))
plt.title("Feature importances")
plt.bar(range(dummy_features.shape[0]), importances[indices], color="cornflowerblue", yerr=std[indices], align="center")
plt.xticks(range(dummy_features.shape[0]), [dummy_features[i] for i in indices])
plt.xlim([-1, dummy_features.shape[0]])
plt.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonの個人的によく使うもののメモ

大学のレポートやAtCoderで(個人的には)よく使う関数を記述していきます.

1. 三角関数

sin, cos, tanや度数法,弧度法の変換
()のなかは基本は弧度法で扱われるようです.
math.piは円周率(π)です.

import math 
x = math.pi/3
s = math.sin(x)
c = math.cos(x)
t = math.tan(x)
#s = 0.8660254
#c = 0.5000000
#t = 1.73205 

弧度法,度数法を扱いたい場合はmath.radians, math.degreesを使います.

import math
rad = math.radians(60)
deg = math.degrees(math.pi)
c = math.cos(math.radians(60))
#rad = 1.04710
#deg = 59.9999999
#c = 0.500000

2. 対数関数

math.log(真数,底)が使えますが底を指定しない場合は,底はeでか計算されmath.(なんちゃって)
底が2と10の場合は下のように書くこともできるようです.

import.math
l_e = math.log(1)#底はe(ネイピア数)
l_2 = math.log2(4)#底は2
l_10 = math.log10(1000)#底は10

l_3 = math.log(81,3)
#l_e = 0.0
#l_2 = 2.0
#l_10 = 3.0

#l_3 = 4.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonの個人的によく使うもののメモ(入力,三角関数,対数)

大学のレポートやAtCoderで(個人的には)よく使う関数を記述していきます.

0. 競プロでの入力に使うものたち

1つの整数や少数,文字列は○○(input())次のように受け取ります.○○は型です

N = int(input)#整数
f = float(input())#少数
S = str(input())#文字列

1行で複数の入力を受ける場合,map(〇〇, input().split())を使います.
split()を忘れるとREを喰らうので気をつけてください.(自戒)

a,b,c = map(int, input().split()) 
s,t,u = map(str, input().split())

最後にリストを受け取る時はlist(map(〇〇, input().split()))を使います.

A = list(map(int, input().split()))
S = liat(map(int, input().split())

1. 三角関数

sin, cos, tanや度数法,弧度法の変換
()のなかは基本は弧度法で扱われるようです.
math.piは円周率(π)です.

import math 
x = math.pi/3
s = math.sin(x)
c = math.cos(x)
t = math.tan(x)
#s = 0.8660254
#c = 0.5000000
#t = 1.73205 

弧度法,度数法を扱いたい場合はmath.radians, math.degreesを使います.

import math
rad = math.radians(60)
deg = math.degrees(math.pi)
c = math.cos(math.radians(60))
#rad = 1.04710
#deg = 59.9999999
#c = 0.500000

2. 対数関数

math.log(真数,底)が使えますが底を指定しない場合は,底はeでか計算されmath.(なんちゃって)
底が2と10の場合は下のように書くこともできるようです.
ネイピア数はmath.eで使えます.

import.math
l_e = math.log(1)#底はe(ネイピア数)
l_2 = math.log2(4)#底は2
l_10 = math.log10(1000)#底は10

l_3 = math.log(81,3)
#l_e = 0.0
#l_2 = 2.0
#l_10 = 3.0

#l_3 = 4.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS 7にPython 3.8をインストール(SCL)

はじめに

Software Collection(SCL)を利用してCentOS7にPython 3.8をインストール
参考:Quick Start — Software Collections

サポート

本手法で導入した場合、Red Hat Software Collections Product Life Cycle - Red Hat Customer Portalより、2023-05がEOLだと思われる。
それ以降に報告された脆弱性や不具合への対応は実施されない可能性がある。

LOG

レポジトリ登録

# yum install -y centos-release-scl

インストール

# cat /etc/redhat-release
CentOS Linux release 7.8.2003 (Core)

# yum install -y rh-python38 which
# scl enable rh-python38 bash
... 略

各種確認

# which python{,2,3}
/opt/rh/rh-python38/root/usr/bin/python
/usr/bin/python2
/opt/rh/rh-python38/root/usr/bin/python3

# ll /opt/rh/rh-python38/root/usr/bin/python*
lrwxrwxrwx. 1 root root     9 Jun 24 10:31 /opt/rh/rh-python38/root/usr/bin/python -> ./python3
lrwxrwxrwx. 1 root root     9 Jun 24 10:31 /opt/rh/rh-python38/root/usr/bin/python3 -> python3.8
-rwxr-xr-x. 1 root root 15280 May 28 09:39 /opt/rh/rh-python38/root/usr/bin/python3.8

# python -V
Python 3.8.0

# python3 -V
Python 3.8.0

# yum info rh-python38
Loaded plugins: fastestmirror, ovl
Loading mirror speeds from cached hostfile
 * base: ftp.riken.jp
 * centos-sclo-rh: ftp.riken.jp
 * centos-sclo-sclo: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
Installed Packages
Name        : rh-python38
Arch        : x86_64
Version     : 2.0
Release     : 4.el7
Size        : 0.0
Repo        : installed
From repo   : centos-sclo-rh
Summary     : Package that installs rh-python38
License     : GPLv2+
Description : This is the main package for rh-python38 Software Collection.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミングサークルでの活動記録

この記事について

コンピュータ研究会セキュリティ部門でやったことを、まとめながらアウトプットのために書きます。
毎週金曜日にどんどん追加していきます。

6月19日

活動内容:
https://google-gruyere.appspot.com/ のpart3, part4 までやりました。

課題:Path Traversal Attack を体験できるウェブアプリケーションの作成

ディレクトリ構成
.
├── templates
│   └── index.html
├── a.txt
├── b.txt
├── c.txt
├── pass.txt
└── app.py
index.html
<!doctype html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Hello Jinja2</title>
</head>

<body>
    <h1>Path Traversal Attack を体験できるウェブアプリケーション</h1>
    <p>下のフォームに表示したいファイルのファイル名を入力してください</p>
    <p><strong>a.txt</strong> <strong>b.txt</strong> <strong>c.txt</strong> の中から選べます。</p>

    <form action="/" method="POST" enctype="multipart/form-data">
        <div>
            <label for="name">ファイル名:</label>
            <input type="text" id="name" name="name" placeholder="名前">
        </div>
        <div>
            <input type="submit" value="送信">
        </div>
    </form>
    <p>{{data}}</p>
    <br><br><br>
    <h4>解説</h4>
    <p>このサイトでは、受けとたったファイル名を持つファイルをディレクトリ内で探し、そのまま返しています</p>
    <p>よって、入力が予想されないファイル名が入力されてしまった時極秘ファイルを返してしまう可能性があります。</p>
    <h3>pass.txtと入力してみましょう</h3>
</body>

</html>
a.txt
これは a.txt の中身です。
b.txt
これは b.txt の中身です。
c.txt
これは c.txt の中身です。
pass.txt
貴重なパスワードのデータを抜き取ることができました。
app.py
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/', methods=['POST'])
def post():
    name = request.form.get('name')
    data = ""
    try:
        f = open(name)
        data = f.read()
        f.close()
    except:
        pass

    return render_template('index.html', data = data)

if __name__ == '__main__':
    app.run()

ローカルサーバーを立てれば良いだけだったので、Flaskを使って書きました。とんでもなく簡単に実装できたので、さすがFlask!って感じでした。
、、てかこんなアホな脆弱性のある実装する人本当にいるのでしょうか、、?

6月12日

活動内容:
https://google-gruyere.appspot.com/ のpart0, part1, part2 までやりました。

学んだこと:
XSS(クロスサイトスクリプティング)には、反射型、蓄積型、DOM based xssなど様々な種類がある。
XSSの対策、サニタイジング(エスケープ)をする。

課題: DOM Based XSS を体験できるウェブページの作成

xss.html
<html>
  <title>DOM Based XSS</title>
  <h1>DOM Based XSSが体験できるサイト</h1>
  Hi
  <script charset="UTF-8">
    var pos=document.URL.indexOf("name=")+5;
    document.write(unescape(document.URL.substring(pos,document.URL.length)));
  </script>
  <br><br>
  <p>このページのurlの最後にパラメータとして名前をもたせると、動的にhtmlを書き換えます</p>
  <p>(例) 〜〜xss.html?name=Taro</p>
  <p>一番上のHiの後に名前が表示されたと思います。</p><br>
  <P>しかしこのサイトではパラメータに悪意のあるスクリプト入れられたときにそれを実行してしまいます。</P>
  <p>(例) 〜〜xss.html?name=&ltscript&gtalert("Your PC was broken!!")&lt/script&gt</p>
  <P>alertができるということは、実際に悪影響を及ぼすスクリプトを実行させられてしまいます。</P>
  </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]Seleniumで動的に変化する属性をXPATH(またはCSSセレクタ)で指定する

スクレイピングをする際に属性が動的に変化する要素を指定する際のノウハウ。

HTMLサンプル

<input id="sample_123456789">

このような要素を毎回指定したいケースはよくあると思いますがXPATHかCSSセレクタを使用すると動的に指定する事が出来ます。

XPATHを使用する

element = driver.find_elements_by_xpath('//*[starts-with(@id,"sample_")]')

このように記述すると動的に指定する事が出来ます。

CSSセレクタを使用する

element = driver.find_elements_by_css_selector("input[id^=sample_]")
element = driver.find_elements_by_css_selector("input[id*=sample_]")

CSSセレクタでも同様に指定出来るみたいです。

参考リンク

要素特定のテクニック
【Selenium】動的に変わるidの取得方法
python selenium: iterate through radio buttons that have dynamic ids and select

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]BeautifulSoupで属性を前方一致検索で指定して要素を取得する

スクレイピングする際に属性の値が動的になっている要素を取得する際に少しハマったので共有。

HTMLサンプル

<div class="hogehoge">
    <input type="hidden" id="sample_1" value="fugafuga">
    <input type="hidden" id="sample_2" value="fugafuga">
    <input type="hidden" id="sample_3" value="fugafuga">
    <input type="hidden" id="sample_4" value="fugafuga">
    <input type="hidden" id="sample_5" value="fugafuga">
    ・
    ・
    ・
</div>

このような形で要素数が不定で属性の値が動的になっている要素を取得したいとする。

取得方法

・lambda関数ver

source = soup.find_all('input', id=lambda value: value and value.startswith('sample_'))

lambda関数を使用して前方一致検索で要素を取得出来る。

・正規表現ver

source = soup.find_all('input', id=re.compile('^sample_'))

正規表現で取得も可能。

・CSSセレクタver

source = soup.select('input[id^=sample_]')

CSSセレクタで指定する事も可能。

参考リンク

How to find all divs who's class starts with a string in BeautifulSoup?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Grand Contest 046 復習

今回の成績

スクリーンショット 2020-06-24 13.06.37.png

今回の感想

A問題が予想外に簡単で驚きました。
B問題は結果的に自分の解法を貫けばよかったので、水diffや青diffの問題に対する自信を付けれればACできるようになるのではと思っています。

A問題

スピードはそこそこで解けたのでよかったと思います。

下図のように、始点を複素数平面の原点Oとすると、偏角が$+x$されることがわかります。また、操作後に常に同一円周上にあるのは明らかなので偏角が操作開始時と等しければよく、$k$回の操作後に$l$周して時に原点Oにたどり着くような$k$のうち最も小さいものを考えればよいです。

IMG_0424.JPG

したがって、$kx=360 l \leftrightarrow k=\frac{360l}{x}$が成り立ちます。ここで、$k$は整数なので、$360l$は$x$かつ$360$の倍数であり最小の$l$を考えれば良いので最小公倍数になります。以上より、$360$と$x$の最小公倍数を$x$で割ったものが答えになります。

A.py
from math import gcd
def lcm(a,b):
    return a//gcd(a,b)*b
x=int(input())
print(lcm(360,x)//x)

B問題

解けないのではという心配により焦って解いてしまいました…。

まず、色々誤読して、都合の良いようなパターンを探そうとしました。誤読は良くないですが、都合の良いパターンをまず探すのは悪くはない方針だと思います。ここでは順番に実は関係なく塗られ方を直接考えれば良いのでは?行の方と列の方のどちらかを塗ったのかで場合分けすれば良いのでは?などと考えましたが、考察の結果難しいとわかりました。
↑ここに30分くらい使ったのはだめですね…。

上記の方針から次に動的計画法による方針に移りました。この方針に移ったのは、行または列を追加するだけなので状態を管理すれば良いことと遷移は(D+C)-(B+A)回のみであることが理由になります。

ここで、DPの配列はdp[i][x,y]=(i回の操作で行がxで列がyとなるような塗られ方の総数)とすれば良いのは明らかだと思います。また、DPの遷移は行を追加するのか列を追加するのかで変わってきます。前者の場合はdp[i][x,y]→dp[i+1][x+1,y]で後者の場合はdp[i][x,y]→dp[i+1][x,y+1]になります。

この時、遷移の際に被るパターンの塗り方が存在するので、dp[i+1][x,y+1]からとdp[i+1][x+1,y]からのdp[i+2][x+1,y+1]への遷移を下図で考えます。(この被るパターンをしっかり考察せずに対称性を利用してサンプルに合わせようとしていました。反省して次に生かします。)

IMG_0425.JPG

ここで、$x+1$行目と$y+1$列目において被るパターンが発生することが上図よりわかるので、共通部分である$x \times y$の行列を考えると考察を進めることができると考えました。

さらに、$(x+1)\times y$の行列と$x \times (y+1)$行列の二つから$(x+1)\times (y+1)$行列へと遷移させる際に被るパターンは$x\times y$の行列から$(x+1)\times (y+1)$行列を生成する際に$(x+1)\times y$の行列と$x \times (y+1)$行列のどちらを経由しても作り出せるようなパターンと言い換えることができ、下図のようなパターンになります。

IMG_0426.JPG

よって、上図の被る部分を消せば良いですが、"$x+1$がAになる場合と$y+1$がBになる場合は被りが発生しないこ"と及び"$x+1$がCを超える場合と$y+1$がDを超える場合は存在しないこと"、の二つに注意して実装すると二つ目のコードになります。

二つ目のコードでは、dpの結果を保存するコンテナでmapを使っており、アクセスに対数時間かかるので計算量的にギリギリです。ここでは、mapを使わず通常の配列を使うことで5~10倍程度の高速化を行うことができ、dpを保存するコンテナの要素の定義は以下のように変化します。

dp[i][x,y]=(i回の操作で行がxで列がyとなるような塗られ方の総数)

dp[i][j]=(i回の操作で行がjだけ増えた場合の塗られ方の総数)

よって、x=j+Aでy=i-j+Bであり、インデックスに注意して実装を行えば先ほどと同様の議論をすることができ、一つ目のコードになります。

B_AC.cc
//インクルード(アルファベット順)
#include<algorithm>//sort,二分探索,など
#include<bitset>//固定長bit集合
#include<cmath>//pow,logなど
#include<complex>//複素数
#include<deque>//両端アクセスのキュー
#include<functional>//sortのgreater
#include<iomanip>//setprecision(浮動小数点の出力の誤差)
#include<iostream>//入出力
#include<iterator>//集合演算(積集合,和集合,差集合など)
#include<map>//map(辞書)
#include<numeric>//iota(整数列の生成),gcdとlcm(c++17)
#include<queue>//キュー
#include<set>//集合
#include<stack>//スタック
#include<string>//文字列
#include<unordered_map>//イテレータあるけど順序保持しないmap
#include<unordered_set>//イテレータあるけど順序保持しないset
#include<utility>//pair
#include<vector>//可変長配列

using namespace std;
typedef long long ll;

//マクロ
//forループ関係
//引数は、(ループ内変数,動く範囲)か(ループ内変数,始めの数,終わりの数)、のどちらか
//Dがついてないものはループ変数は1ずつインクリメントされ、Dがついてるものはループ変数は1ずつデクリメントされる
#define REP(i,n) for(ll i=0;i<(ll)(n);i++)
#define REPD(i,n) for(ll i=n-1;i>=0;i--)
#define FOR(i,a,b) for(ll i=a;i<=(ll)(b);i++)
#define FORD(i,a,b) for(ll i=a;i>=(ll)(b);i--)
//xにはvectorなどのコンテナ
#define ALL(x) (x).begin(),(x).end() //sortなどの引数を省略したい
#define SIZE(x) ((ll)(x).size()) //sizeをsize_tからllに直しておく
//定数
#define INF 1000000000000 //10^12:極めて大きい値,∞
#define MOD 998244353 //10^9+7:合同式の法
#define MAXR 100000 //10^5:配列の最大のrange(素数列挙などで使用)
//略記
#define PB push_back //vectorヘの挿入
#define MP make_pair //pairのコンストラクタ
#define F first //pairの一つ目の要素
#define S second //pairの二つ目の要素

template<ll mod> class modint{
public:
    ll val=0;
    //コンストラクタ
    modint(ll x=0){while(x<0)x+=mod;val=x%mod;}
    //コピーコンストラクタ
    modint(const modint &r){val=r.val;}
    //算術演算子
    modint operator -(){return modint(-val);} //単項
    modint operator +(const modint &r){return modint(*this)+=r;}
    modint operator -(const modint &r){return modint(*this)-=r;}
    modint operator *(const modint &r){return modint(*this)*=r;}
    modint operator /(const modint &r){return modint(*this)/=r;}
    //代入演算子
    modint &operator +=(const modint &r){
        val+=r.val;
        if(val>=mod)val-=mod;
        return *this;
    }
    modint &operator -=(const modint &r){
        if(val<r.val)val+=mod;
        val-=r.val;
        return *this;
    }
    modint &operator *=(const modint &r){
        val=val*r.val%mod;
        return *this;
    }
    modint &operator /=(const modint &r){
        ll a=r.val,b=mod,u=1,v=0;
        while(b){
            ll t=a/b;
            a-=t*b;swap(a,b);
            u-=t*v;swap(u,v);
        }
        val=val*u%mod;
        if(val<0)val+=mod;
        return *this;
    }
    //等価比較演算子
    bool operator ==(const modint& r){return this->val==r.val;}
    bool operator <(const modint& r){return this->val<r.val;}
    bool operator !=(const modint& r){return this->val!=r.val;}
};

using mint = modint<MOD>;

//入出力ストリーム
istream &operator >>(istream &is,mint& x){//xにconst付けない
    ll t;is >> t;
    x=t;
    return (is);
}
ostream &operator <<(ostream &os,const mint& x){
    return os<<x.val;
}

mint dp[6000][3000]={0};

signed main(){
    ll A,B,C,D;cin>>A>>B>>C>>D;
    //i番目はi個の列or行を選んでる
    //a=Aの時が配列のインデックス0に対応
    //dp[i][j]として要素の合計はA+B+i,aの要素の合計はj+A,bの要素の合計はB+i-j
    //j+AはA以上C以下,B+i-jはB以上D以下
    //jは0以上C-A以下、かつ、B+i-D以上i以下
    dp[0][0]=1;
    REP(i,(D+C)-(B+A)){
        FOR(j,max(ll(0),B+i-D),min(C-A,i)){
            if(B+i-j!=D)dp[i+1][j]+=(dp[i][j]*mint(j+A));
            if(A+j!=C)dp[i+1][j+1]+=(dp[i][j]*mint(B+i-j));
        }
        if(i==0)continue;
        FOR(j,max(ll(0),B+i+1-D),min(C-A,i+1)){
            if(j!=0 and i+1!=j){
                dp[i+1][j]-=(dp[i-1][j-1]*mint(j+A-1)*mint(B+i-j));
            }
        }
    }
    cout<<dp[(D+C)-(B+A)][C-A]<<endl; 
}
B_TLE.cc
//インクルード(アルファベット順)
#include<algorithm>//sort,二分探索,など
#include<bitset>//固定長bit集合
#include<cmath>//pow,logなど
#include<complex>//複素数
#include<deque>//両端アクセスのキュー
#include<functional>//sortのgreater
#include<iomanip>//setprecision(浮動小数点の出力の誤差)
#include<iostream>//入出力
#include<iterator>//集合演算(積集合,和集合,差集合など)
#include<map>//map(辞書)
#include<numeric>//iota(整数列の生成),gcdとlcm(c++17)
#include<queue>//キュー
#include<set>//集合
#include<stack>//スタック
#include<string>//文字列
#include<unordered_map>//イテレータあるけど順序保持しないmap
#include<unordered_set>//イテレータあるけど順序保持しないset
#include<utility>//pair
#include<vector>//可変長配列

using namespace std;
typedef long long ll;

//マクロ
//forループ関係
//引数は、(ループ内変数,動く範囲)か(ループ内変数,始めの数,終わりの数)、のどちらか
//Dがついてないものはループ変数は1ずつインクリメントされ、Dがついてるものはループ変数は1ずつデクリメントされる
#define REP(i,n) for(ll i=0;i<(ll)(n);i++)
#define REPD(i,n) for(ll i=n-1;i>=0;i--)
#define FOR(i,a,b) for(ll i=a;i<=(ll)(b);i++)
#define FORD(i,a,b) for(ll i=a;i>=(ll)(b);i--)
//xにはvectorなどのコンテナ
#define ALL(x) (x).begin(),(x).end() //sortなどの引数を省略したい
#define SIZE(x) ((ll)(x).size()) //sizeをsize_tからllに直しておく
//定数
#define INF 1000000000000 //10^12:極めて大きい値,∞
#define MOD 998244353 //10^9+7:合同式の法
#define MAXR 100000 //10^5:配列の最大のrange(素数列挙などで使用)
//略記
#define PB push_back //vectorヘの挿入
#define MP make_pair //pairのコンストラクタ
#define F first //pairの一つ目の要素
#define S second //pairの二つ目の要素

template<ll mod> class modint{
public:
    ll val=0;
    //コンストラクタ
    modint(ll x=0){while(x<0)x+=mod;val=x%mod;}
    //コピーコンストラクタ
    modint(const modint &r){val=r.val;}
    //算術演算子
    modint operator -(){return modint(-val);} //単項
    modint operator +(const modint &r){return modint(*this)+=r;}
    modint operator -(const modint &r){return modint(*this)-=r;}
    modint operator *(const modint &r){return modint(*this)*=r;}
    modint operator /(const modint &r){return modint(*this)/=r;}
    //代入演算子
    modint &operator +=(const modint &r){
        val+=r.val;
        if(val>=mod)val-=mod;
        return *this;
    }
    modint &operator -=(const modint &r){
        if(val<r.val)val+=mod;
        val-=r.val;
        return *this;
    }
    modint &operator *=(const modint &r){
        val=val*r.val%mod;
        return *this;
    }
    modint &operator /=(const modint &r){
        ll a=r.val,b=mod,u=1,v=0;
        while(b){
            ll t=a/b;
            a-=t*b;swap(a,b);
            u-=t*v;swap(u,v);
        }
        val=val*u%mod;
        if(val<0)val+=mod;
        return *this;
    }
    //等価比較演算子
    bool operator ==(const modint& r){return this->val==r.val;}
    bool operator <(const modint& r){return this->val<r.val;}
    bool operator !=(const modint& r){return this->val!=r.val;}
};

using mint = modint<MOD>;

//入出力ストリーム
istream &operator >>(istream &is,mint& x){//xにconst付けない
    ll t;is >> t;
    x=t;
    return (is);
}
ostream &operator <<(ostream &os,const mint& x){
    return os<<x.val;
}


signed main(){
    ll A,B,C,D;cin>>A>>B>>C>>D;
    //i番目はi個の列or行を選んでる
    vector<map<pair<ll,ll>,mint>> dp((D+C)-(B+A)+1);
    dp[0][MP(A,B)]=1;
    REP(i,(D+C)-(B+A)){
        for(auto j=dp[i].begin();j!=dp[i].end();j++){
            ll a,b;a=j->F.F;b=j->F.S;
            if(b!=D){
                dp[i+1][MP(a,b+1)]+=(dp[i][MP(a,b)]*mint(a));
            }
            if(a!=C){
                dp[i+1][MP(a+1,b)]+=(dp[i][MP(a,b)]*mint(b));
            }
        }
        if(i==0)continue;
        for(auto j=dp[i+1].begin();j!=dp[i+1].end();j++){
            ll a,b;a=j->F.F;b=j->F.S;
            if(a!=A and b!=B){
                dp[i+1][MP(a,b)]-=(dp[i-1][MP(a-1,b-1)]*mint(a-1)*mint(b-1));
            }
        }
    }
    cout<<dp[(D+C)-(B+A)][MP(C,D)]<<endl; 
}

C問題以降

今回は飛ばします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spotifyで再生した曲を、再生回数順に並べる

はじめに

普段Spotifyで音楽を聞いていて、再生回数順に並べたいな〜と思うときがあるのですが、再生回数自体アプリでは見られないんですよね。

その代わりに、SpotifyのPC版サイト(https://www.spotify.com/jp/)
で、JSON形式のデータとして、視聴履歴などをダウンロードすることができます。

そこで、自分のデータをダウンロードして、Pythonでカウントして表示してみました。

ダウンロードの手順
プロフィール > プライバシー設定 > お客様のデータのダウンロードからボタンをクリック。
メールでダウンロードリンクが送られてきます。最長30日かかるらしいので、気長に待ちましょう。

標準ライブラリしか使ってないので、プログラミング分からない人でもPCにPythonさえ入っていれば、コピペして実行すれば使えると思います。

自分用に書いたので、動かなかったらすみません。

コード

count.py
import json
import collections

# jsonデータの読み込み
# file_pathには、自分のStreamingHistoryファイルを指定してください。
with open('file_path') as f:
    d = json.load(f)

list = []

print("全体検索しますか? y/n")
search_all = str(input())

print("何回以上を表示しますか?")
count = int(input())

# 曲名の取り出し&リストに追加
# 全体検索の場合
if search_all == "y":
    for i in d:
        list.append(i['trackName'])
# 指定検索の場合
elif search_all == "n":
    print("アーティストを入力してください")
    artist = str(input())
    for i in d:
        if(i['artistName'] == artist):
            list.append(i['trackName'])

# 出現回数順に要素を取得
c = collections.Counter(list)
c_list = c.most_common()

print("------------")
print("曲名, 再生回数")

# 再生回数が一定以上のものを表示する
for i in c_list:
    if i[1] >= count:
        print(i)

表示結果

スクリーンショット 2020-06-24 18.24.25.png

Pythonはさくっと色々できて便利ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]BeautifulSoupでname属性を指定して要素を取得する

BeautifulSoupでhtml要素を取得する際にname属性を指定したい時のお話。

基本的なfind

# 取得例
source = soup.find('div', class_='hogehoge')

基本はこのような形でsoup.find('タグ名', 属性名='値')と指定して取得する。
ですが、name属性でこの記述をするとエラーになる。

# 取得例
source = soup.find('input', name='hogehoge', type='hidden')
実行結果
TypeError: find() got multiple values for keyword argument 'name'

これはBeautifulSoupのfindメソッド内で既にnameという引数を定義している為エラーとなるらしい。

name属性を指定する際の記述

source = soup.find('input', attrs={'name': 'hogehoge', 'type': 'hidden'})

attrsという引数に辞書型で値を渡してあげるとname属性を指定する事が出来るみたいなのでこれで指定する。

source = soup.find('input', {'name': 'hogehoge', 'type': 'hidden'})

引数を省略しても同じ結果が得られるので好きな方を選びましょう。

参考リンク

Parameters for find function
Python:BeautifulSoup-名前属性に基づいて属性値を取得します

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを使ったポケモン種族値の絞り込み検索

ポケモン対戦でトリックルームを使用できるポケモンを探したいと思ったのですが、どのポケモンを使用するか迷っていました。
まず、トリックルームを使用する際に後行になるので相手からの攻撃に耐えられるポケモンが良いかなと思い種族値で素早さが低くて、防御や特防が高いポケモンを探したいと思いました。そこでcsvデータからプログラミング言語のPythonを使って絞り込んで検索しようと思いつきました。

事前に準備すること

・ExcelまたはNumbersのインストール
・Anacodaのインストール
・PythonとPandasのインストール

手順

① csvファイルのダウンロードと作成
ポケモンの種族値のcsvファイルは下記のURLからダウンロードできます。
https://www.kaggle.com/abcsds/pokemon
このcsvファイルは7-8世代ポケモンのデータが入力されていません。また、英語表記なのでポケモンの名前も英語名になっているので多少扱いにくいところもあります。

8世代のポケモン(剣盾内登場ポケモン)は下記のURLからデータをコピーして若干手間がかかりましたが、Excel上で表を整えてcsvファイルにしました。
https://wiki.ポケモン.com/wiki/種族値一覧_(第八世代)

② Pythonで種族値の絞り込み
まずcsvファイルからデータを読み込んでみます。インストールしたcsvファイルのアイコンをドラッグして画面の左のファイルリストに入れます。次に以下のコードをAnacodaのJupyterLabで入力してみます。

import pandas as pd
df = pd.read_csv("Pokemon.csv")
df

すると画面上で以下の図のようにリストが表示されると思います。

スクリーンショット 2020-06-16 14.47.48.png

次に伝説を除いたエスパータイプのポケモンを絞り込んでみたいと思います。次のコードを入力してみます。

df.loc[(df.Legendary == False) & (df["Type 1"] == "Psychic")]

すると以下の画面のように伝説以外のエスパータイプのポケモンのリストが表示されたと思います。

スクリーンショット 2020-06-16 14.52.30.png

そして、先ほどの絞り込んだリスト結果を引き継いでさらに防御種族値が60以上で素早さが65以下のポケモンを検索するために以下のコードを入力します。

df = df.loc[(df.Legendary == False) & (df["Type 1"] == "Psychic")]
df.loc[(df.Defense >= 60) & (df.Speed <= 65)]

入力した結果、かなり絞り込めたと思います。

スクリーンショット 2020-06-16 15.01.23.png

最後に以下のコードを入力して防御種族値を降順に並び替えれば終了です。

df = df.loc[(df.Defense >= 60) & (df.Speed <= 65)]
df.sort_values(by = ['Defense'], ascending = False)

スクリーンショット 2020-06-16 15.04.13.png

まとめると、以下のコードを入力すれば簡単に結果が出てくると思います。

import pandas as pd
df = pd.read_csv("Pokemon.csv")
df = df.loc[(df.Legendary == False) & (df["Type 1"] == "Psychic")]
df = df.loc[(df.Defense >= 60) & (df.Speed <= 65)]
df.sort_values(by = ['Defense'], ascending = False)

最後に

英語名だとポケモンがわかりにくかったのでURLからコピーしたリストで作成した日本語名のcsvファイルも読み込んで同様に絞り込んでみました。ファイル名をPokemon_data.csvにして下記のコードを入力すると結果が出てきます。

import pandas as pd
df = pd.read_csv("Pokemon_data.csv")
df = df.loc[(df.伝説 == False) & (df["タイプ1"] == "エスパー")]
df = df.loc[(df.防御 >= 60) & (df.素早さ <= 65)]
df.sort_values(by = ['防御'], ascending = False)

スクリーンショット 2020-06-16 15.37.40.png

わかったことはブリムオンとムシャーナは素早さが同程度に低くて防御と特防も高かったのでトリックルームを使うポケモンの候補になると自分の中で解釈しました。

他にもゴーストタイプでトリックルームが使えるポケモンもいたりするので同様にコードを入力して探してみるのもいいかもしれません。

あとがき

Pandasを使ったコードの練習は下記のURLで勉強できます。プログラミングの勉強の参考にすると良いと思います。

https://www.kaggle.com/learn/pandas

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】自然数の約数を高速で取得する方法

はじめに

こちらの記事をご覧頂きありがとうございます!!
2020年6月よりプログラミングデビューしたなかぞんです!

毎日、AtCorderのA-D問題を最低5問解くことを日課としています。

私の記事では、プログラミング初心者の目線で、
日々の日課を通じて発見した学びや苦労した事、
嬉しかった事などを発信しようと思います。

同じ初心者の方々に学びのある記事になるよう努めて参ります!
上級者の方は温かい目で見守ると共に、
プラスの知恵やアドバイスを是非ともお待ちしてます!

それでは、本日の内容に参ります!!

問題

本日の学びの元となった問題はこちら

C - Walk on Multiplication Table
https://atcoder.jp/contests/abc144/tasks/abc144_c

image.png

例題のN=10で考えてみると、iとjの候補は (1, 10) (2, 5)の2つ。
N = i * j を満たすiとjの組み合わせでi + jが最小のものを考えればよいですね。
じゃあ「約数を取得しよう!」と考えたところで、
「あれ?どうすればいいんだっけ」状態に陥る。。。笑

自然数Nの約数はこうやって取得する!!

Nが最大10**12なのでfor文で1から全探索するのは
時間がかかりすぎるので工夫が必要です。

qiita.rb
N = int(input())
divisors = []
for i in range(1, int(N**0.5)+1):
    if N % i == 0:
        divisors.append(i)
        if i != N // i:
            divisors.append(N//i)

divisors.sort()

このように√N(約数の折り返し地点)まで探索すれば
全ての約数は取得できるということがポイントですね。

最後に

こういうアルゴリズム素で気づける人になりたい。
てか気づけるやつすげえな

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者に多いPythonエラー 10選

初めてPythonを学ぶ途端、Pythonのエラーメッセージの意味を理解するのは少し複雑かもしれません。よく見るエラーヒントは下記のようにご参照ください。

1)if、elif、else、for、while、class、defの最後に追加するのを忘れた。

エラーヒント:SyntaxError :invalid syntax
エラーは次のコードで発生します。
1.png

2)「=」と「==」の使用

エラーヒント:SyntaxError :invalid syntax
「=」は代入演算子であり、「==」は比較演算である。エラーは次のコードで発生します。
2.png

3)forループでlen()を呼び出すのを忘れた

エラーヒント:TypeError: ‘list’ object cannot be interpreted as an integer
通常、インデックスでlistまたはstringの要素を反復処理します。これには、range()関数を呼び出す必要があります。 このリストを返す代わりに、len値を返すことを忘れないでください。エラーは次のコードで発生します。
3.png

4)string値を変更する

エラーヒント:TypeError: ‘str’ object does not support item assignment
stringは不変のデータ型であり、エラーは次のコードで発生します。
4.png

5)文字列以外の値を文字列に接続する

エラーヒント:TypeError: Can’t convert ‘int’ object to str implicitly
エラーは次のコードで発生します。
5.png

6)文字列の最初と最後に「’」を追加するのを忘れた

エラーヒント:SyntaxError: EOL while scanning string literal
エラー次のコードで発生します。
6.png

7)変数名としてPythonキーワードを使用する

エラーヒント:SyntaxError: EOL while scanning string literal
Pythonキーワードは変数名として使用できません。このエラーは次のコードで発生します:
7.png

8)メソッド名のスペルが間違っている

エラーヒント:AttributeError: ‘str’ object has no attribute ‘lowerr’
このエラーは次のコードで発生します:
8.png

9)参照がリストの最大インデックスを超えている

エラーヒント:IndexError: list index out of range
このエラーは次のコードで発生します:
9.png

10)range()を使用して整数のリストを作成する

range()は実際にlist値ではなく「range object」を返すことを覚えておく必要があります。
エラーヒント:TypeError: ‘range’ object does not support item assignment
このエラーは次のコードで発生します:
10.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeError: unsupported operand type(s) for /: 'list' and 'float'

タイトルにあるエラーを解決

内包表記で作ったリストに定数をかけたところ、エラーが発生。np.array()で括ってnumpyで計算させたら解決スクリーンショット 2020-06-24 16.10.45.png

スクリーンショット 2020-06-24 16.11.33.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データフレームに格納された複数行テキストの最大文字数を調べる

メモ

スクリーンショット 2020-06-24 15.21.31.png

mojisuu = []
for i in range(len(input_data)):
    mojisuu.append(len(input_data.iloc[i,0]))
print(max(mojisuu))
#最大文字数の文章は122文字
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データサイエンス100本ノック~初心者未満の戦いpart5

これはデータサイエンティストの卵がわけもわからないまま100本ノックを行っていく奮闘録である。
完走できるか謎。途中で消えてもQiitaにあげてないだけと思ってください。

100本ノックの記事
100本ノックのガイド

ネタバレも含みますのでやろうとされている方は注意

ココでグダグダ書いているのはネタバレ防止に1ページ分くらいの尺を稼いでいるからです()

今回は脱線に脱線を重ね、自分が何を調べたいのかわからなくなりました(原因は30本目)

コレは見づらい!この書き方は危険!等ありましたら教えていただきたいです。心にダメージを負いながら糧とさせていただきます。

今回は29~32まで。
[前回]23~28
[目次付き初回]

今回は分からないこと多かったです

29本目

P-029: レシート明細データフレーム(df_receipt)に対し、店舗コード(store_cd)ごとに商品コード(product_cd)の最頻値を求めよ。

最頻値はモードというのは分かっていましたが、前回の続きで
groupby(store_cd).agg({'product_cd':['mode']})
と書くとコケます。というか、aggのなかには無いっぽい?

いつものサイトにヘルプして調べていてもなかなかうまくいかない。
なんでだ?と調べているとこんな記事

以下のサイトにも書かれていますが、groupbyした後にmodeで最頻値を求める場合は、value_countsやapplyを組み合わせる必要があります。英語ですが、コードの例はシンプルですぐに理解できると思いますので、参考にしてみてください。
https://github.com/pandas-dev/pandas/issues/11562
df.groupby('grouping_content').最頻を求めたい対象.apply(lambda x: x.mode())

ラムダェ……

mine29.py
df=df_receipt
df=df.groupby('store_cd').product_cd.apply(lambda x: x.mode()).reset_index()
df.head(5)

うん、わからん

30本目

P-030: レシート明細データフレーム(df_receipt)に対し、店舗コード(store_cd)ごとに売上金額(amount)の標本分散を計算し、降順でTOP5を表示せよ。

問題文連投してる時点で察してください

まず、数学が高2で苦手になり10年以上経過した自分としてはシグマの上下左右につらつらと文字列や大きな記号が書いてあると死にかけます
そんな自分はExcelで標準偏差なんか求める時も基本コピペ

そんな中出てきた「標本分散」の文字

分散

なんか書いてあると白眼になるよね。ならない?

標準分散の公式はこんな感じ

variance178.png

死にかけた眼をコスりながらとりあえずプログラムっぽく書いて頭を整理します

ikinokoru.py
def Sno2jo(X_list):

   n = len(X_list)
   X_ave = sum(X_list) / n
   ret = 0.0

   for X in range(X_list):
      ret += (X - X_ave)**2

   ret = ret / n
   return ret

こんな感じか……?(配列は0番開始としています)(動作確認してません)

プログラムならいける

その後、いろいろサイト動画を見ているうちになんとなーく理解していきましたが大きく脱線したので切り上げてノックにもどります。

今回は統計関数を使えばきっと楽なのだろうと思い(そのためにPythonしてるわけだし)調べて解きます

参考

mine30.py
df=df_receipt
df= df.groupby('store_cd').amount.var(ddof=0).reset_index().sort_values('amount',ascending=False).head(5)
df

'''模範解答'''
df_receipt.groupby('store_cd').amount.var(ddof=0).reset_index().sort_values('amount', ascending=False).head(5)

'''失敗例'''
df=pd.concat([df['store_cd'],df.groupby('store_cd').amount.var(ddof=0)],axis=0)

失敗例では
image.png
こんな感じの結果になったので、うまく結合できなかったのかと。単体でいいと思い、自分で回答できました。

いやー、時間かかった

31本目

mine31.py
df=df_receipt
df= df.groupby('store_cd').amount.std(ddof=0).reset_index().sort_values('amount',ascending=False).head()
df

30のをそのままコピペで突破
ddof=FalseだとIntで渡せと言われて0にしました。
ddofここ動画から考えるに一致推定量か不偏推定量の違いかと。

32本目

パーセンタイル値の意味が分からなかった(なんとなくは分かったが)ので、調べてから参考サイトを調べる(流れるようなカンニング)

参考

mine32.py
df=df_receipt
df['amount'].quantile([0.0,0.25,0.5,0.75,1.0])

'''模範解答1'''
np.percentile(df_receipt['amount'], q=[25, 50, 75,100])

'''模範解答2'''
df_receipt.amount.quantile(q=np.arange(5)/4)

一応方向としては模範解答2に近いですが、np.arangeをつかうやり方はなるほどと思った。

今回はここまで

もし今回のCSVを使って遊んでみたい人用
保存先\100knocks-preprocess\docker\work
ここに各種CSVあります

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

14歳の長男に出題するPythoのF○○○B○○○

まえがき

登場人物

  • Ruby、JavaScript好きぐらいでPythonは学習したことがない父親の自分
  • スプラトゥーンとマイクラが大好きな長男

ある日の父子の会話

長男:プログラミングやりたい
俺:なぜ急に?
長男:なんとなく
俺:マイクラの影響?
長男:Youtubeでなんとなく興味を持った
俺:どんなことやりたい?
長男:とりあえずプログラムやりたい
俺:言語どうする?
長男:とと(父の意)は何がいいと思う?

俺:うーん(お手軽にJavaScriptと言ってしまうか?それともRubyを勧めるか?いや、流行りはPythonだろか?質問があっても困らないところにしたい。CとJavaは困る)

俺:RubyかPythonはどうだろうか?
長男:パイソンで!(即答
俺:なぜにパイソン?
長男:聞いたことあるから

強いなPython、中学生でも知っているとは。とりあえず初心者向けの本を購入して長男に渡した。

Python 1年生 体験してわかる!会話でまなべる!プログラミングのしくみ (Amazon)

課題を出したい

学習したからには課題を出したい。
初心者に出す課題といったらF○○○B○○○で決まりでしょう。F○○○B○○○ダンジョンを思い出します。

課題

1から100まで、3で割りけれるときはF○○○、5で割り切れるときはB○○○、3と5で割り切れるときはFizzBuzzを出力する。

自由回答

最初は自由に回答させます。

条件付きの課題

Python初心者の自分が長男に課題を出すなら、父親なりの回答を用意しておきたいと思います。

基本形

これを利用することを条件とします。

for i in range(1,101):
    list = [i, "F○○○", "B○○○", "F○○○B○○○"]
    #課題

1:制限なし

制限なしの回答例

for i in range(1,101):
    list = [i, "F○○○", "B○○○", "F○○○B○○○"]
    if i%15==0:
        num=3
    elif i%5==0:
        num=2
    elif i%3==0:
        num=1
    else:
        num=0
    print(list[num])

2:一行

ikkoじゃないよ、1行で書く回答例

for i in range(1,101):
    list = [i, "F○○○", "B○○○", "F○○○B○○○"]
    print(list[3 if i%15==0 else 2 if i%5==0 else 1 if i%3==0 else 0])

3:if禁止、行数制限なし

if禁止、行数制限なしの回答例

for i in range(1,101):
    list = [i, "F○○○", "B○○○", "F○○○B○○○"]
    num = list('300102100120100')
    print(str[int(num[i%15])])

4:if禁止、一行

if禁止、1行で書く回答例

for i in range(1,101):
    list = [i, "F○○○", "B○○○", "F○○○B○○○"]
    print(list[bool(i%5==0)*2 + bool(i%3==0)])

5:%禁止

だめだ、分からなかった。

for i in range(1,101):
    list = [i, "F○○○", "B○○○", "F○○○B○○○"]
    # %禁止のコードはわからなかった

おわりに

ここまで考えたんだから、長男よ、ちゃんと学習して俺に課題を出さしてくれ。
伏字にしたのはぐぐって見つからないように願っている

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最小二乗法の多項式の係数を求める

概要

最小二乗法

最小二乗法は、誤差を伴う測定値の処理において、その誤差の二乗の和を最小にすることで、関係式を求める方法です。

プログラムについて

今回作成したプログラムは、予め用意されたデータx yをリストにし、そのデータから行列S Tを求め、係数aを導く処理となります。また、求めたい多項式の次数をmと置いています。
最小二乗法を簡単に求めることができるライブラリは既に存在するかもしれませんが、今回はそのようなライブラリは利用せず、配列操作ライブラリnumpyのみを利用し、データ量を変更しても融通が利くような処理を作成しました。

プログラム

LeastSquares.py
# -*- coding: utf-8 -*-
#!/usr/bin/env python3

import numpy as np

X_data = [10, 20, 50, 70, 100]
Y_data = [9.3, 9.8, 10.9, 11.9, 13.1]
m = 1

def ST_list(power):
    S, T = 0, 0
    for i, j in zip(X_data,Y_data):
        S += i**power
        T += i**power * j
    return S, T

def A_matrix(n_size, len):
    A = []
    for k in range(n_size):
        A.append(S_list[k+len-m*2-1:k+len-m*2-1+n_size])
    return A

S_list = []
T_list = []
for n in range(len(X_data)):
    S , T = ST_list(n)
    S_list.insert(0, S)
    T_list.insert(0, T)

A = np.array(A_matrix(m+1, len(S_list)))
b = np.array(T_list[-1*m-1::]).reshape(m+1, 1)
Ainv = np.linalg.inv(A)
x = np.dot(Ainv, b)

print("S ="); print(A); print()
print("T ="); print(b); print()
print("a ="); print(x)

実行結果

一次多項式の係数aを求める
m = 1
S =
[[17900   250]
 [  250     5]]

T =
[[2977.]
 [  55.]]

a =
[[0.04203704]
 [8.89814815]]

二次多項式の係数aを求める
m = 2
S =
[[130430000   1477000     17900]
 [  1477000     17900       250]
 [    17900       250         5]]

T =
[[2.2141e+05]
 [2.9770e+03]
 [5.5000e+01]]

a =
[[1.22729504e-05]
 [4.07142857e-02]
 [8.92034855e+00]]

まとめ

今回のデータでは、一次多項式と二次多項式のときの係数aを求めました。データ量を増やせば三次、四次も行うことができますが、二次多項式のように浮動小数の影響を受けるため、見やすい値にするにはroundformatを利用する必要がありそうです。

終わりに

今回の記事はここまでになります。
見にくいプログラムで申し訳ないです。特に配列操作に関してはもっと良い方法があるように感じます。アドバイス等あれば、コメント欄にてお願いいたします。
最後までお付き合い頂きありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】Qオブジェクトを用いた際のエラー(Related Field got invalid lookup)

はじめに

 検索機能を実装する際、Qオブジェクトを用いて絞り込みを行った。ここで、Related Field got invalid lookupというエラーが出たので原因を記録しておく。

状態

モデルはPostモデルを用意し、フィールドとして投稿者、タイトル、内容、投稿日をもつ。

app/models.py
class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.TextField()
    published_at = models.DateTimeField(auto_now_add=True)
app/urls.py
app_name = 'app'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]
app/index.html
    <!--検索フォーム-->
    <div>
        <form action="" method="get">
            <a type="submit"></a>
            <input name="query" value="{{ request.GET.query }}" type="search" placeholder="Search...">
        </form>
    </div>
    <!--検索フォーム-->

 q_word = self.request.GET.get('query')で検索フォームの中身を取得し、存在する場合はQオブジェクトを用いてindex.htmlにレンダリングするobject_listを絞り込む。存在しない場合は単純にobject_listを返す。

app/views
class IndexView(generic.ListView):
    model = Post
    template_name = 'app/index.html'

    def get_queryset(self):
        q_word = self.request.GET.get('query')

        if q_word:
            object_list = Post.objects.filter(
                Q(author__icontains=q_word) |
                Q(title__icontains=q_word) |
                Q(content__icontains=q_word)
            )
        else:
            object_list = Post.objects.all()
        return object_list

原因

 エラーの原因はapp/views.pyのQ(author__icontains=q_word)の部分である。views.pyのIndexViewクラスで検索の対象としてauthor、title、contentを指定しているが、models.pyのPostモデルでauthorフィールドはForeignKey()でユーザーモデルと紐づけられている。したがって、authorフィールドに紐づくモデルのフィールドを指定する必要がある。つまり、
Q(author__icontains=q_word)ではなくQ(author__username__icontains=q_word)と変更し、authorフィールドに紐づくUserモデルのusernameフィールドを検索対象とする必要がある。

app/views
class IndexView(generic.ListView):
    model = Post
    template_name = 'app/index.html'

    def get_queryset(self):
        q_word = self.request.GET.get('query')

        if q_word:
            object_list = Post.objects.filter(
                Q(author__username__icontains=q_word) |    # 変更!!
                Q(title__icontains=q_word) |
                Q(content__icontains=q_word)
            )
        else:
            object_list = Post.objects.all()
        return object_list
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest: D問題解答集 Python

AtCoder Beginner Contest 171

collections.defauldict(int) を使って、各数字の個数を数え上げておく

import collections

N = int(input())
A = sorted(map(int, input().split()))

cnt = collections.defaultdict(int)
for a in A:
    cnt[a] += 1
ans = sum(A)

Q = int(input())
for _ in range(Q):
    B, C = map(int, input().split())
    ans += (C - B) * cnt[B]
    cnt[C] += cnt[B]
    cnt[B] = 0

以降追加予定

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Atcoder Beginner Contest の A, B 問題でありがちな入力まとめ Python

整数 $N$ を入力する
例:1

N = int(input())

整数の組 $A, B, C$ を入力する
例:1 4 3

A, B, C = map(int, input().split())

整数の組 $A_1, A_2, A_3, \cdots$ を入力する
例:1 4 3 2 5 2

# list型
A = list(map(int, input().split()))  # [1, 4, 3, 2, 5, 2]

# list型(昇順に並び替え)
A = sorted(map(int, input().split()))  # [1, 2, 2, 3, 4, 5]

# set型(重複を削除)
A = sorted(map(int, input().split()))  # {1, 2, 3, 4, 5}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Machine Learningで使用するAnaconda仮想環境構築とJupyterとの連携方法

はじめに

Azure Machine Learning上のJupyter notebooksを使用して機械学習環境を構築するための方法をまとめたものです。

Azure Machine Learning StudioとJupyter notebooksの起動が行える状態を前提としています。
仮想環境の構築や切り替えにはAnacondaを使用します。

Anacondaとは

データサイエンス向けのPythonパッケージ集合体
- 1,500以上のパッケージに対応
- GUIとコマンドラインの両方に対応
- condaコマンドを使って操作できる

Anacondaを使うことでライブラリのインストール等に時間を取られることなく簡単に開発環境の構築や切り替えを行うことが可能です。

参考:https://www.creativevillage.ne.jp/72837

手順

1. jupyter notebooks上でTerminalを起動

image.png

2. Anaconda仮想環境のセットアップとJupyter連携

以下手順はJupyter上のTerminalに入力していきます。

2.1 condaによる仮想環境の構築

$ conda create --name my_notebook_env python=3.7 -y

-name my_notebook_env:仮想環境の名前. 好きなものを設定できる
python=3.7:仮想環境で使用するPythonバージョン
-y:確認項目に自動的にyesで応答する

※conda環境の一覧を表示:jupyter kernelspec list

2.2 仮想環境の有効化

$ conda activate my_notebook_env

※無効化するには:conda deactivate

2.3 仮想環境内でのライブラリのインストール

Azure Machine Learning SDK for Python や他の必要なライブラリをインストールします。

$ pip install pandas jupyter
$ pip install --upgrade azureml-sdk[explain,automl,interpret,notebooks]

インストールしてるコンポーネントの詳細については下記をご覧ください。
Azure Machine Learning SDK for Python

2.4 Jupyterへの仮想環境カーネルの追加

仮想環境をアクティベートした状態のまま下記コマンドを実行します。

$ ipython kernel install --user --name=my_notebook_env --display-name=my_notebook_env

--user:現在のユーザー環境にインストール
--name:カーネルの名前を指定
--display-name:カーネルの表示名。今回はカーネル名と同一にしたが、ここに分かりやすい表示名を設定できる。

※Jupyterカーネル一覧の表示:jupyter kernelspec list
※カーネルの削除:jupyter kernelspec uninstall my_notebook_env

3. Jupyter notebooks上でのカーネル切り替え

3.1 Jupyterのリロード

変更を反映させるため、Jupyter notebooksを一度shutdownするなどして再読み込みを行います。

3.2 カーネルの切り替え

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む