pandasにおけるデータの連続判定

第一章 序章

1.1 はじめに

 近年,機械学習やDeepLearning等のAIの分野の発展に伴ってプログラミング言語の中でもPythonの人気や需要は高まっている. その中でもPythonフレームワークであるpandasはAI等の学習における説明変数の整形やビックデータ等のデータ分析などで多々使われるツールである.
 しかし,プログラミング言語等々の初学者やソースコードを書くのに少し慣れてきた者にとっては便利であるがゆえに知識不足や経験不足から遠回りしやすく,データの大きいものを扱いがちな分野であるがゆえに処理速度が著しく低下したり,使用できるメモリを使い果たしてしまうことも考えられる.
 また,例え自ら調べたとしてもその書かれた記事等が数年前の記事であることも珍しくなく,一方でプログラミング言語等のバージョンは頻繁にアップデートされているため書かれている記事の内容を丸写しして実行してもうまくいかないことも残念ながらありうる.
 そこで,筆者が経験したことの中でサイト等記事になっていないことの中から"pandasにおけるデータの連続判定"について取り扱い,自分と同じように困っている人がいるかもしれないと思い記事にした.

1.2 目的

 Pythonでビックデータ等を扱う際,pandasというフレームワークを用いることでデータ分析を簡単に行うことが可能だ.しかし,ソースコードの如何によってはメモリ使用量を多く消費したり,処理速度が非常に遅くなってしまうケースが存在する.そのため処理速度を速くしたり,メモリ使用量を少なくしようと改善することはソースコードを書いている人にとってはそのソースコードの目的が何であれ共通の課題となりうる.その結果として,わからないこと等が出てきた際には調べるのが定石だが,調べていても自分の知りたいことが記事にされている保証はどこにもないのである.
 今回,筆者が上記のように陥った”pandasにおけるデータの連続判定”においては調べても”pandasにおけるデータの連続値のカウント”について述べている記事しか見つからなかった.連続判定と連続値のカウントは厳密に違うが,それらを応用することで目的のソースコードの記述はどちらであっても可能である.
 そこで”pandasにおけるデータの連続のカウント”の記事で記されていたソースコードと筆者が提案する”pandasにおけるデータの連続判定”を行うソースコードをメモリ使用量と処理速度の観点から複数のデータを用いて比較し,筆者が提案するソースコードの方が良いと考えられた場合,この記事と通してそのソースコードを勧めることで同じような内容で困っている方がこの記事を見つけて少しでも役に立つことが目的である.

 実際に”pandasにおけるデータの連続値のカウント”について”stackoverflow”や”Qiita”で書かれていたソースコードを以下に示す.

import pandas as pd

df = pd.DataFrame({'group': [1, 1, 2, 3, 3, 3, 3, 2, 2, 4, 4, 4],'value': [1, 2, 1, 1, 3, 2, 4, 2, 1, 3, 2, 1]})  

y = df['group']
df['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1

 ソースコードの1行目でpandasのインポートを行い,2行目に使用するDataframe型のデータを定義しており,実際に連続のカウントを行っているのは3行目以降である.  3行目のy = df['group']で対象にするDataframeのcolumnを設定することで4行目にcolumnが等しいグループにおいての”value”coulmnが連続している場合におけるカウントをdf変数の新しいcolumn”new”にカウント値を格納する.
 4行目の処理を詳しく説明すると,3行目で対象にしたDataframeのcolumnに対してgroupby()を行うことでgroupの値が等しいもの同士での処理が行われる.また,y != y.shift()で次の行の”group”の値が一致しているかを確認し,一致している行に対して累積和cumsum()を求め,それのカウントに1を足すcumcount()+1ことで連続値のカウントが可能である.

第二章 本論

 2.1 目的

今回の目的は”pandasにおけるデータの連続値のカウント”と検索した際に見られるソースコード

import pandas as pd

df = pd.DataFrame({'group': [1, 1, 2, 3, 3, 3, 3, 2, 2, 4, 4, 4],  'value': [1, 2, 1, 1, 3, 2, 4, 2, 1, 3, 2, 1]})
  
y = df['group']
df['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1

と筆者が提案するソースコードとの処理速度,メモリ使用量の検証.それに応じた各ソースコードのメリットデメリットについて考察することを目的とする.

 2.2 方法

 実験の実行環境は以下の通りである。

  • OS: Windows 10 Enterprise 64bit
  • プロセッサ:Intel Core i3-8130U 2.20 GHz
  • メモリ(RAM):4GB
  • ハードディスク:256GB SSD
  • Python 3.7.3
  • pandas 0.24.2

 2.3 結果

 2.3.0 準備

 用いるDataframe型のデータを

    repeat_time = 10  # 繰り返す回数
    dataframe_len = 10000  # Dataframeのindexの長さ

    for i in range(repeat_time):
        random.seed(i)
        group_list = [random.randint(0, 10) for j in range(dataframe_len)]
        random.seed(i+1)
        value_list = [random.randint(0, 10) for j in range(dataframe_len)]
        sample_df = pd.DataFrame({'group': group_list,
                                  'value': value_list})

 ランダムな整数を生成させたものをデータをした.

 2.3.1 処理速度の検証結果

 処理速度の観点において以下の連続値のカウントを行うソースコードを実行させた.

# 連続値のカウントを行うソースコード
import pandas as pd
import time
import random


def chk_dataframe_series(df):  # 比較するソースコード
    y = df['group']
    df['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1


if __name__ == '__main__':

    repeat_time = 10  # 繰り返す回数
    cum_execute_time = 0  # 累計処理時間
    dataframe_len = 10000  # Dataframeのindexの長さ

    for i in range(repeat_time):
        # データ作成
        random.seed(i)
        group_list = [random.randint(0, 10) for j in range(dataframe_len)]
        random.seed(i+1)
        value_list = [random.randint(0, 10) for j in range(dataframe_len)]
        sample_df = pd.DataFrame({'group': group_list,
                                  'value': value_list})

        start = time.time()  # 処理開始時間
        chk_dataframe_series(sample_df)
        end = time.time()  # 処理終了時間
        execute_time = end - start  # 1回の処理時間
        cum_execute_time += execute_time  # 累計処理時間
        print('one time runtime(', i+1, '):', execute_time)  # 1回の処理時間表示

    print('average runtime:', cum_execute_time/repeat_time)  # 10回の処理時間の平均表示

 上記の連続値のカウントを行うソースコードの実行結果は以下の通りであった. f:id:RandyGen:20210304155623p:plain

 一方同じ観点において,以下の筆者が提案するデータの連続判定を行うソースコードを実行させた.

# データの連続判定を行うソースコード
import pandas as pd
import time
import random


def chk_dataframe_series(df):  # 比較するソースコード
    sorted_df = df.drop_duplicates(subset=['group', 'value']).sort_values(['group', 'value'])
    created_df = sorted_df[(sorted_df.group != sorted_df.group.shift()) & (sorted_df.value.diff() != 1)]


if __name__ == '__main__':

    repeat_time = 10  # 繰り返す回数
    cum_execute_time = 0  # 累計処理時間
    dataframe_len = 10000  # Dataframeのindexの長さ

    for i in range(repeat_time):
        # データ作成
        random.seed(i)
        group_list = [random.randint(0, 10) for j in range(dataframe_len)]
        random.seed(i+1)
        value_list = [random.randint(0, 10) for j in range(dataframe_len)]
        sample_df = pd.DataFrame({'group': group_list,
                                  'value': value_list})

        start = time.time()  # 処理開始時間
        chk_dataframe_series(sample_df)
        end = time.time()  # 処理終了時間
        execute_time = end - start  # 1回の処理時間
        cum_execute_time += execute_time  # 累計処理時間
        print('one time runtime(', i+1, '):', execute_time)  # 1回の処理時間表示

    print('average runtime:', cum_execute_time/repeat_time)  # 10回の処理時間の平均表示

 上記のデータの連続判定を行うソースコードの実行結果は以下の通りであった. f:id:RandyGen:20210304155448p:plain

 また,上記の検証で用いた2×10000ではなくデータサイズによって処理速度が変化すると考えられることから,データサイズを2×100,2×1000,2×100000に変更して実行した際の実行結果を以下に示す.

 連続値のカウントを行うソースコード(2×100,2×1000) f:id:RandyGen:20210304160448p:plain f:id:RandyGen:20210304160521p:plain f:id:RandyGen:20210304175253p:plain

 データの連続判定を行うソースコード(2×100,2×1000) f:id:RandyGen:20210304160822p:plain f:id:RandyGen:20210304160617p:plain f:id:RandyGen:20210304175407p:plain

 2.3.1検証結果総括
データサイズ 2×100 2×1000 2×10000 2×100000
連続値カウント [sec] 0.00337 0.00429 0.00688 0.02927
連続判定 [sec] 0.00409 0.00474 0.00444 0.01019

 2.3.2 メモリ使用量の検証結果

また,メモリ使用量の観点において以下の連続値のカウントを行うソースコードを実行させた.

# 連続値のカウントを行うソースコード
import pandas as pd
import random
from memory_profiler import profile


@profile  # メモリ使用量検証用
def chk_dataframe_series(df):  # 比較するソースコード
    y = df['group']
    df['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1


if __name__ == '__main__':

    repeat_time = 10  # 繰り返す回数
    cum_execute_time = 0  # 累計処理時間
    dataframe_len = 1000  # Dataframeのindexの長さ

    # データ作成
    random.seed(0)
    group_list = [random.randint(0, 10) for j in range(dataframe_len)]
    random.seed(1)
    value_list = [random.randint(0, 10) for j in range(dataframe_len)]
    sample_df = pd.DataFrame({'group': group_list,
                              'value': value_list})

    chk_dataframe_series(sample_df)

 上記の連続値のカウントを行うソースコードの実行結果は以下の通りであった. f:id:RandyGen:20210304161841p:plain

 一方同じ観点において,以下の筆者が提案するデータの連続判定を行うソースコードを実行させた.

# データの連続判定を行うソースコード
import pandas as pd
import random
from memory_profiler import profile


@profile  # メモリ使用量検証用
def chk_dataframe_series(df):  # 比較するソースコード
    sorted_df = df.drop_duplicates(subset=['group', 'value']).sort_values(['group', 'value'])
    created_df = sorted_df[(sorted_df.group != sorted_df.group.shift()) & (sorted_df.value.diff() != 1)]


if __name__ == '__main__':

    repeat_time = 10  # 繰り返す回数
    cum_execute_time = 0  # 累計処理時間
    dataframe_len = 10000  # Dataframeのindexの長さ

    # データ作成
    random.seed(0)
    group_list = [random.randint(0, 10) for j in range(dataframe_len)]
    random.seed(1)
    value_list = [random.randint(0, 10) for j in range(dataframe_len)]
    sample_df = pd.DataFrame({'group': group_list,
                              'value': value_list})

    chk_dataframe_series(sample_df)

 上記のデータの連続判定を行うソースコードの実行結果は以下の通りであった. f:id:RandyGen:20210304162233p:plain

 2.3.2検証結果総括
全体 1行目 2行目
連続値カウント [MiB] 1.6 0.1 1.5
連続判定 [MiB] 0.5 0.5 0.0

 2.3.3 追加検証

 さらに,先ほど用いたデータと異なり同じグループとみなすcolumnが”group”のみではなく”group1,group2”と複数になったケースを考慮して以下の連続値のカウントを行うソースコードを実行させた.

# 連続値のカウントを行うソースコード
import pandas as pd
import random


def chk_dataframe_series(df):  # 比較するソースコード
    y = df['group1', 'group2']
    df['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1


if __name__ == '__main__':

    repeat_time = 10  # 繰り返す回数
    cum_execute_time = 0  # 累計処理時間
    dataframe_len = 100  # Dataframeのindexの長さ

    # データ作成
    random.seed(0)
    group1_list = [random.randint(0, 10) for j in range(dataframe_len)]
    random.seed(1)
    group2_list = [random.randint(0, 10) for j in range(dataframe_len)]
    random.seed(2)
    value_list = [random.randint(0, 10) for j in range(dataframe_len)]
    sample_df = pd.DataFrame({'group1': group1_list,
                              'group2': group2_list,
                              'value': value_list})

    chk_dataframe_series(sample_df)

    print('execute complete')

 上記の連続値のカウントを行うソースコードの実行結果は以下の通りであった. f:id:RandyGen:20210304165437p:plain

 一方同じ観点において,以下の筆者が提案するデータの連続判定を行うソースコードを実行させた.

# データの連続判定を行うソースコード
import pandas as pd
import random


def chk_dataframe_series(df):  # 比較するソースコード
    sorted_df = df.drop_duplicates(subset=['group1', 'group2', 'value']).sort_values(['group1', 'group2', 'value'])
    created_df = sorted_df[(sorted_df.group1 != sorted_df.group1.shift())
                           & (sorted_df.group2 != sorted_df.group2.shift())
                           & (sorted_df.value.diff() != 1)]


if __name__ == '__main__':

    repeat_time = 10  # 繰り返す回数
    cum_execute_time = 0  # 累計処理時間
    dataframe_len = 100  # Dataframeのindexの長さ

    # データ作成
    random.seed(0)
    group1_list = [random.randint(0, 10) for j in range(dataframe_len)]
    random.seed(1)
    group2_list = [random.randint(0, 10) for j in range(dataframe_len)]
    random.seed(2)
    value_list = [random.randint(0, 10) for j in range(dataframe_len)]
    sample_df = pd.DataFrame({'group1': group1_list,
                              'group2': group2_list,
                              'value': value_list})

    chk_dataframe_series(sample_df)

    print('execute complete')

  上記のデータの連続判定を行うソースコードの実行結果は以下の通りであった. f:id:RandyGen:20210304165711p:plain

 2.3.3検証結果総括
実行
連続値カウント 不可
連続判定

 2.4 考察

 2.4.1 2.3.1の検証結果の考察

 2.3.1の検証結果から2×10000のデータに対して連続値のカウントを行うソースコードの処理時間が0.00688 [sec],連続判定を行うソースコードの処理時間が0.00444 [sec]と連続判定を行うソースコードの方が処理時間が短かった.
 また,データサイズを2×100,2×1000,2×10000,2×100000と変更して処理を実行した結果,連続値のカウントを行うソースコードの処理時間が0.00337 [sec],0.00429 [sec],0.00688 [sec],0.02927 [sec]となったのに対して,連続判定を行うソースコードの処理時間は0.00409 [sec],0.00474 [sec],0.00444 [sec],0.01019 [sec]と連続値のカウントを行うソースコードの処理時間はデータサイズが大きさに比例して処理時間が長くなったが一方連続判定を行うソースコードの処理時間は連続値のカウントを行うソースコードの処理時間に比べ,データサイズの大きさに比例して長くなる処理時間の度合いが小さかった.
 これらのことから処理時間においてデータサイズが大きくなればなるほど処理時間の差は開くだろうと考えられる.

 2.4.2 2.3.2の検証結果の考察

 2.3.2の検証結果から2×10000のデータに対して連続値のカウントを行うソースコードのメモリ使用量が1.6 [MiB],連続判定を行うソースコードの処理時間が0.5 [MiB]と連続判定を行うソースコードの方がメモリ使用量が少なかった.
 また,各ソースコードにおいて連続値のカウントを行うソースコードではdf['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1, 連続判定を行うソースコードではsorted_df = df.drop_duplicates(subset=['group', 'value']).sort_values(['group', 'value'])の処理がほとんどのメモリ使用量を占めていた.
 今回の検証では連続判定を行うソースコードにおいて変数sorted_dfに対するメモリの解放を処理に加えなかったため,ソースコードdel sorted_dfを加えることでよりメモリ使用量を減少させることが可能であると考えられる.

 2.4.3 2.3.1と2.3.2の双方の検証結果に対する考察

 2.4.2の考察で各ソースコードにおいて連続値のカウントを行うソースコードではdf['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1, 連続判定を行うソースコードではsorted_df = df.drop_duplicates(subset=['group', 'value']).sort_values(['group', 'value'])の処理がほとんどのメモリ使用量を占めていたことから,2つのソースコードの処理時間の差もこれらのdf['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1sorted_df = df.drop_duplicates(subset=['group', 'value']).sort_values(['group', 'value'])が引き起こしているものだと考えられる.
 よって今回の2つのソースコードにおける処理速度とメモリ使用量の差はpandasのAPIであるsortdrop,またshiftcumsumcumcountのデータ応じた挙動を追求することでより厳密に解決できると考えられる.

 2.4.4 2.3.3追加検証の結果に対する考察

 2.3.3の検証結果から同じグループとみなすcolumnが”group”のみではなく”group1,group2”と複数になったデータを用いた場合,連続値のカウントを行うソースコードではKeyError: ('group1','group2')となり正常な処理が行われなかったのに対して,連続判定を行うソースコードではexecute completeと出力され正常に処理が行われたことが分かった.
 連続値のカウントを行うソースコードでエラーが発生した原因はdf['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1の処理においてy = df['group1', 'group2']と複数のcolumnにしてしまったことでyの型がpd.Seriesから変更されてしまいcumcountはpd.Seriesに対して処理を行うため結果的にKeyErrorとなってしまったと考えられる.
 このことから連続値のカウントを行うソースコードでは連続判定を行うソースコードに比べてgroupbyを用いていることから他への応用が利きにくい,柔軟性に欠けたソースコードなっていると考えられる.

 2.5 結論

 pandasにおける連続値のカウントを行う

import pandas as pd

df = pd.DataFrame({'group': [1, 1, 2, 3, 3, 3, 3, 2, 2, 4, 4, 4],
                              'value': [1, 2, 1, 1, 3, 2, 4, 2, 1, 3, 2, 1]})  
y = df['group']
df['new'] = y.groupby((y != y.shift()).cumsum()).cumcount() + 1

 上記のソースコードより以下のpandasにおける連続判定を行う

import pandas as pd

df = pd.DataFrame({'group': [1, 1, 2, 3, 3, 3, 3, 2, 2, 4, 4, 4],
                              'value': [1, 2, 1, 1, 3, 2, 4, 2, 1, 3, 2, 1]})  

sorted_df = df.drop_duplicates(subset=['group', 'value']).sort_values(['group', 'value'])
created_df = sorted_df[(sorted_df.group != sorted_df.group.shift()) & (sorted_df.value.diff() != 1)]

 このソースコードの方が処理速度,メモリ使用量,応用の利く柔軟性があるという点において優れている.

 2.6 文献

https://qiita.com/Masutani/items/3cea640da7d1f5f58af1

  • pandasにおけるデータの連続値のカウントのソースコード(stackoverflow)

https://stackoverflow.com/questions/27626542/counting-consecutive-positive-value-in-python-array

  • pandasの各API(shift,cumsum,cumcount)

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.shift.html

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.cumsum.html

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.core.groupby.GroupBy.cumcount.html

 2.7 資料

 今回使用したソースコードGithubにて公開しています.

https://github.com/RandyGen/pandasVerification/tree/main/pandasVerification

データ分析の長期インターン備忘録 ~僕とfor文とIterator(イテレータ)~

初めての長期インターン


 2月から始めた長期インターンが気づけば半年以上過ぎていました。
 業務内容はデータ分析に関することで、Pythonを触ったことがあるものの、「Pandas?何それおいしいの?」状態から始めて色々試行錯誤して続けてきました。初めてのこともあって学べることが多かったのでここでPandasの色々についてまとめようと思い記事にしました。

注)今回の記事は主にPandas(Dataframe)についての記事です。
長期インターンのあれこれについては書かないので、長期インターンの感想を聞きたい、参考にしたいという方はご容赦ください。

博識な皆さんへ


 私はとてつもなくググるのが下手です。まして知識がないので自分の知りたい情報に行き着けず、いつも遠回りしています。さらに、PCも学校からの借り物でよわよわです。そのため、現時点では下に示すようなコードが一番早く、メモリ消費を抑えているつもりです。
 もっといいコードがあったらTwitter等でも構わないので是非教えてほしいです。


 本題に戻ります。

え...もしかして私のコードfor文多すぎ...?


 今でも課題の一つですが、データ分析のデータ量というのは基本的に膨大なものです。自分の業務はその膨大なデータから条件に合うデータを抽出するもので、csvにして約1GBものデータなのでネストのないfor文でも数千万loopもしてしまうため基本的にfor文等はあまり褒められたものじゃないんだとか...
 まあ、当然ですよね
 でも、使わないとコード書けないよーってことで、少しでもfor文の処理を速める、メモリの使用量を減らすために着目したが"iterator(イテレータ)"です。
 C++の授業でやったやんけぇ...


注)jupyter labで実行しました%%timeitはjupyterだから可能なのかも...


x = 10000
size = x*10

df = pd.DataFrame(pd.np.arange(size).reshape(x, 10))
df.columns = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

%%timeit
list = process_for(df)  


 仮のDataframe(df)、10000×10のものを作成してcolumnsを割り当てました。このprocess_forという自作関数の実行速度について様々なfor文を用いて実行してみました。まずは以下のコード


def process_for(df):
     arr = []
      for i in range(0, len(df)):
           if df.iloc[i, 0] >= 50:
                arr.append([df.iloc[i, 0], df.iloc[i, 1]])

 Dataframeを扱い始めて間もないころ、df.atdf.loc, df.ilocでDataframeの各要素の値を持ってこれることを知ったのでそれを用いてfor文を書いていました。

 その結果...

  • 624 ms ± 21.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


 当時は( ´_ゝ`)フーンって感じ、では次のように書き換えて

def process_for(df):
     arr = []
      for row in df.itertuples(name=None):
           if row[1] >= 50:
                arr.append([row[1],row[2]])

 Iteratorについて調べてみたところitertuples()iterrows()があり、

itertuples()はiterrows()よりも速い

 という記述をとあるサイトで見つけたのでitertuples()を採用したところ...

  • 30.6 ms ± 1.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


 全然ちゃうやんけ...なんでもっとはやく知らなかったんだ...
 そこでさらに調べてみて以下のコードに書き換えてみました

def process_for(df):
     arr = []
      for a, b in zip(df['a'], df['b']):
            if a >= 50:
                   arr.append([a, b])

 必要な列だけを用いたzip()を使ったfor文に変えました、すると

  • 9.17 ms ± 1.46 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)


 zip最強!また、zip(df.index)で列番号を取得できます。zipしか勝たん!!!


 10000×10のDataframeの前に、よりサイズの小さい10×10や100×10でも試してみましたが、速度の順位が覆ることなく、サイズが大きくなればなるほど差が開いていくばかりでした。
 また、Iterator(イテレータ)を用いたときの変数(process_forのrowやa, bのことを指すこととします)の型はDataframeの要素に応じたもので、今回用いたDataframe(df)では数値が要素に入っているのでint型でした。
 (てっきりpd系の型がついているのかと思った...)

 for文なんて別にどれも一緒だろうと思っていた自分にとっては驚きでした。Iterator(イテレータ)は知っていたものの速度等に差はないだろwwと思っていた自分を殴ってやりたいですね。
 これが一番早いなど思っていてもまずは疑ってかかることの大事さに気づかせてくれたので非常によかったです。

アディショナルタイム


 先のprocess_forのコードはDataframeから条件にあった列の要素を抽出してListにしていますが、本当はListではなくもう一度Dataframeにしたいのです。しかし、どうやったら一度でDataframeにできるかわからず、遠回りになってしまいますが、一度Listにしてからpd.Dataframe()で型を変換してDataframeにしています。
 もし、抽出したコードを一度でDataframeにする方法をご存知のかたは教えてほしいです。よろしくお願いいたします。


 つまらぬ内容、拙い文章ですが、最後までお読みいただきありがとうございました。

インターン備忘録~その2~

tambourine(タンバリン)1dayインターン


 先日書いた、インターン備忘録~その1~『tambourine(タンバリン)24Hインターン』の同企業さんが行っていた1dayインターンにも参加したので、そのお話です。
 最近、ブログを書くとそれなりに見てくれていることに味を占めて、ここぞとばかりに書くことにハマっています。
 内容は、1dayインターンの内容にまつわる話です。

企業さんの話やtambourine(タンバリン)24Hインターンの話はこちら

randygen.hatenablog.com

当日の流れ


 インターンは夕方からのスタートで、最初の30分ぐらいは説明で50分作業+10分休憩を3セット、最後に成果発表とパーティー(?)の合計4時間」の構成になっていました。
 インターンには約15人ほど参加していて、基本的に2~3年生の専門学生や大学生で、中には大阪から参加しに来たという方もいらっしゃいました。
 ちなみに、メンター方は3人ほどいらっしゃいました。

Ready…?


 今回のインターンは5~6人のグループに分かれて『モブプログラミング(mob programming)』と呼ばれるチーム開発の方法で行いました。
 『モブプログラミング』とは、2012年頃にHunter Industies社が始めた開発方法で、1台のPCをモニター等に繋げて一人が手を動かしてコードを作成して、残りの人がそのコードに対してアドバイスをしたり、調べ事をしていく方法です。
 自分の感じた『モブプログラミング』のメリット・デメリットは、

  • メリット
    1.フロー効率向上
    2.情報共有、共通認識の形成
  • デメリット
    1.単純作業による時間効率
    2.複数人による意思決定の問題
    3.メンバーの出入りによる作業効率の悪さ

と考えました。

Let's インターン


 いざ、4~5人の3グループに分かれてスタートです。今回は、『ボウリングのスコアを計算する』コードを作成しました。(ボウリングのスコア計算は自分で調べてみてください)
 今回、話し合って参加者の得意な人が非常に多かった"JavaScript"で書きました。初めてでわけわかんなかった......でも、言語が違えど基本の「for文」「if文」のアルゴリズムが出来れば、あとはJSの書ける人に託せば完成できます。
 自分の考えた最初のアプローチは、全て必要なとこをリスト(配列)にすれば行けるなーと軽く考えていました。

# python

throw_ball = []
score = []

# 入力部
for i in range(1, 11):
 if i <= 9:
  for j in range(1, 3):
   x = input()
   a = int(x)
   if a != 10:
    y = input()
    b = int(y)
   else:
    b = -1
   throw_ball.append([a, b])
 if i == 10:
  for j in range(1, 4):
   x = input()
   a = int(x)
   y = input()
   b = int(y)
   if x+y >= 10:
    z = input()
    c = int(z)
   else:
    z = 0
   throw_ball.append([a, b, c])
  
# 計算部
score_x = 0
strike = -1
spare = 10

for i in range(0, 10):
 if throw_ball.iloc[i, 1] != strike or throw_ball.iloc[i, 0] + throw_ball.iloc[i, 1] != spare:
  score_x += throw_ball.iloc[i, 0] + throw_ball.iloc[i, 1]
 elif throw_ball.iloc[i, 1] == strike and throw_ball.iloc[i+1, 1] != strike:
  score_x += throw_ball.iloc[i, 0] + throw_ball.iloc[i+1, 0] + throw_ball.iloc[i+1, 1]
 elif throw_ball.iloc[i, 1] == strike and throw_ball.iloc[i+1, 1] == strike:
  score_x += throw_ball.iloc[i, 0] + throw_ball.iloc[i+1, 0] + throw_ball.iloc[i+2, 0]
 elif throw_ball.iloc[i, 0] + throw_ball.iloc[i, 1]  == spare:
  score_x += throw_ball.iloc[i, 0] + throw_ball.iloc[i, 1] + throw_ball.iloc[i+1, 0]
 score.append(score_x)
 print(score.iloc[i])

 といった感じに頭の中で描いてましたが、もちろん周りを気にせず一人で作るのはこのインターンの意図するところではないので、妥協点を探りながら作っていきました。(上記のコードはストライクの連続等を加味していません。不完全のものです。)最初の50分、自分はアドバイス側だったので、アドバイスしつつ順調に仕上がっていきました。
 しかし、最初の10分休憩で最大の難所が来ました。
 グループ移動です。先ほど挙げたデメリットの1つですが、実際の現場ではない話じゃありません。ランダムで3つあったグループのうちの残り2グループの片方に移動になりました。軽く自己紹介を済まし、2回目の50分の作業スタートです。
 ヤバい。正直、移動した先の班のコードを見て思った感想がこれでした。ストライク、スペアの判定に"flag"を用いて割と複雑な処理になっていました。そのため、まず理解に軽く時間を取られてしまいました。
 残り時間は70分程度、上記のような自分があらかじめ考えていたコードに書き換えれば完成も余裕で間に合います。が、それをしてしまっては、モブプログラミングの意味がありません。現時点までに前の人たちが書いてくれたコードに手を加え、活かしていくのが課題です。しかし、うまく活かそうとしても自分の考えたコードが脳内をちらついてしまい、切り替えが難しかったです。
 結果から言うと他の2班は完成したものの、その移動後の僕の班だけコードが完成しませんでした。印象としては、ストライクの連続したときの処理など、先のことを考えすぎてより複雑なコードになってしまい、自分で自分の首を絞めた感じがしました。あの場にいた班のメンバーは皆さんとても優秀な方でしたが、それゆえにいい妥協点が見つからず、一進一退を繰り返してしまった気がします。(あくまで自分の感想です)

感想


 自分は、1年の夏休みにUnityで2DシューティングゲームC#)を作り、12月頃にはサイトからスクレイピングした内容をマルコフ連鎖で文章生成してツイートするTwitterbotを作成しました。それは、つまずいたら意見を聞いたりしたものの基本は1人での製作でした。そのため、今回のような『モブプログラミング』によるチーム開発の難しさを体験することは初めてでした。でも、実際の現場はこのようなチーム開発をする機会は少なくないので、次やるときには今回の反省を踏まえたうえで、より効率よく作業ができればいいなと思いました。

 以下に今回のインターンで思ったことを箇条書きにして、この記事を締めたいと思います。

  • 良かった点

    • めったにできないチーム開発を体験できた。
    • コードを完成するという共通目的があったため、初対面でも打ち解けるのが早かった。
    • 課題の難易度がちょうどいいものだった。プログラミングに触れたことある人ならもってこい。
    • 最後に一緒にピザを食べるパーティー(?)みたいのがあって、そこで改めて今回参加した人たちやメンターの方とお話ができる。
  • 思ったこと

    • プログラミングやったことのない人にはさすがにきついものがあると思う。for文、if文、リスト(配列)、ぐらいの知識はあるといい。
    • 話し合いに積極的に参加することが大事。同じメンバーにたくさん質問したり、意見すること。
    • アルゴリズムの大事さを改めて知った。定期的に競プロをやるべし。
    • この会社の雰囲気等が知りたいならこのインターンより24Hインターンの方がおススメ

 こんな拙い文章の備忘録にもかかわらず、読んでくださった方、ありがとうございました。
 もっと詳しく知りたい等意見ございましたら、twitterからDMくださると嬉しいです。

今回のインターン先の『tambourine(タンバリン)』さん

www.tam-bourine.co.jp

Wantedly

www.wantedly.com

3/26(木)にオンライン説明会の開催を予定しているそうです。興味のある方は是非!!!!!!
オンライン説明会 | 人の役に立つ技術を追求したい新卒エンジニア大募集!

www.wantedly.com

前回のブログ

randygen.hatenablog.com

うp主

https://twitter.com/RandyGen0905

インターン備忘録 ~その1~

tambourine(タンバリン)24Hインターン


 やることもなく、時間ができたので久しぶりのブログです。
 先日終わった初めてのインターンの内容でも忘れないようにまとめておこうかというきっかけで書き始めることにしました。
 内容は、インターンの紹介と業務内容、感想になります。

経緯


 大学に入学し、プログラミングに触れ初めて早1年。
 いろんな経験をさせてもらい、大学ってこんな感じなんだなと慣れてきたころ、予備校時代から通ってたバイト先から多めの休みをいただいたき、特にやることもなくなった1年の春休み。
 一念発起してインターンにいくことを決め、友人から教えてもらった長期インターン座談会という学生団体のイベントに参加し、企業さんと1対1でお話ししたり、メールを通じてインターンに参加することが決定しました。

いざインターン


 メールで日程を決めた後、オフィス見学&今後の予定決めに初めてのオフィスへ行きました。
 ちなみに、今回のインターン先の企業さんは『tambourine(タンバリン)』さん。JR御茶ノ水駅から徒歩数分の所にオフィスがありました。
ホームページはこちら

www.tam-bourine.co.jp

 オフィスはすごく綺麗でまるでおしゃれなレストランのようでした。それも相まって、とても緊張していたのを覚えています(笑)でも、社員さんはどなたも優しく接してくださったのですぐに緊張はほぐれていきました。

 今回のインターン24Hインターンというもので、勤務時間が合計24時間になるように予定を組み、その期間内に何か形になるものを作るというものでした。僕の場合、6時間×4日間のスケジュールでした。今思えば、初めてにはちょうどいい期間設定だったなと思います。

何したの?


 初オフィス見学の翌週からいよいよ出勤です。メンターの方から業務は「自分のやりたいことでいいよ」と言われたので驚愕…
 てっきり社内環境の改善のためのアプリ開発だと思っていた僕には嬉しい朗報でした(笑)
 そこで今回の業務は『twitterbotをherokuサーバにスケジューラを付けてデプロイする』ことを実現することにしました。簡単に言うと、『twitterbotを自動で1時間ごとにツイートする』ことです。
 デプロイとは、サーバーにシステムを利用可能な状態にして配置することです。

(twitterbotは年末にあらかじめ作っていたのでそこの話は割愛)

そのtwitterbotとやら twitter.com

ぎょーむ


 まず、herokuのアカウント作成!!!
 と思っていたのですが既に作成済みだったので終了。ホントQiita最強 (笑)
 で、次に自分の予め作っていたコードをサーバに渡し自動で実行してもらう訳ですが、今回の業務のポイントは、

1. TwitterAPIのkey(botを使うための個人情報の番号みたいなの)を隠さなければならない
2. そもそもmecabがデプロイできない
3. スケジューラを設定する

 特に2が難所。とても時間がかかったところです。
 残念ながら、メンターの方は一人しかおらず、インターン生も2~3人いたのでさすがにつきっきりで見てもらうこともできず、基本は一人で調べて、試しての繰り返しの作業でした。半分以上は調べる時間に費やしましたね(笑)

1. TwitterAPIのkeyを隠す


 今のままでは個人情報ダダ洩れなので、『.env』というテキストファイルを作ってそこを参照するようにコードを変えてあげます。

//.env

consumer_key='~~~~~~~'
consumer_secret='~~~~~~~'
access_token_key='~~~~~~~~'
access_token_secret='~~~~~~~~'
//setting.py

import os
from os.path import join, dirname

if not os.environ.get('APP'):
    from dotenv import load_dotenv
    load_dotenv(verbose=True)
    dotenv_path = join(dirname(__file__), '.env')
    load_dotenv(dotenv_path)

CONSUMER_KEY = os.environ.get("CONSUMER_KEY")
CONSUMER_SECRET = os.environ.get("CONSUMER_SECRET")
ACCESS_TOKEN_KEY = os.environ.get("ACCESS_TOKEN_KEY")
ACCESS_TOKEN_SECRET = os.environ.get("ACCESS_TOKEN_SECRET")

 if文はローカルとサーバーを分けるために作ったもので、深くは触れないです。
 これで、とりあえず各keyは『.env』の中に納めたのでそれをsetting.pyから参照して

//bot.py
import settings

api = twitter.Api(consumer_key=settings.CONSUMER_KEY,
                  consumer_secret=settings.CONSUMER_SECRET,
                  access_token_key=settings.ACCESS_TOKEN_KEY,
                  access_token_secret=settings.ACCESS_TOKEN_SECRET
                  )

 デプロイするコード(bot.py)からも参照できるようにしてあげることで、各keyを隠すことができます。
 しかし、これで全てのコードをデプロイしては意味がないので、デプロイしないコードを『.gitignore』というテキストフォルダにまとめます。

//.gitignore
*.csv
/all_recipes.csv
/all_recipes.txt
/connect.py
/crow.py
/lording.py
/naginagi.py
.idea
.env
__pycache__/

 今回は、ツイートするコード(bot.py)のみのデプロイなので他のコードも『.gitignore』にまとめます。
 これで、1の準備は万全です。
(今回のサーバーはherokuだったため、herokuの設定からkeyを設定する方法もありました。てか、そっちの方が簡単。)

2. そもそもmecabがサーバーに渡せない


 今回のtwitterbotは形態素解析に『mecab』を用いたものとなっており、毎ツイートごとに形態素解析を行い文章を生成するので、『mecab』をサーバーに渡せる必要があります。
 しかし、バージョンが変わってしまったせいかなぜか渡せなかったので、対策として『mecab』を使わない方法を模索しました。
 そこで『mecab』の代わりに『natto』に変えてみたもののあえなく惨敗。原因は『natto』も『mecab』依存のものであったためかと考えられます。
 さらに、代わりに『nagisa』を用いると「あら不思議!」解決しました。正直謎。わかる人いたら教えてください。

(仕様だと思われますが、『nagisa』は分かち書きの際の半角スペースっぽいのが残ってしまいます。『mecab』にはないんですがね...)

3. スケジューラを設定する


 ここはあまり書くことないです(笑)調べれば出てきます。
 ここの部分もそうですが、先にも述べたようにとにかく調べまくりました。的確な検索ワードじゃないと自分の知りたいことは出てきません。なので、なんて検索したらいいかわからないから的確な検索ワードにするために調べる、みたいなことの繰り返しで非常に時間がかかりました。
 でも、それを怠っては成長しない。色んな方から言われた助言でもあり、今回とても身に染みて感じたことでした。

感想


 そして、気づけば最終日。なんとか完成しました。自分の技量に自信がなかったのでやりたいこと、触ったことのある言語でやらせてもらえてよかったです。
 この24Hインターン、条件として何かしらのプロダクトを作成している必要があります。なので、プロダクトを作ってみたが、実際の現場に触れたいって人の一歩目にはとてもおすすめです。逆に、24時間だけなので何年も研究してますとか、たくさんのプロダクトを一人で作っていますっていう人が、厳しい社会を経験しておきたいというのには若干の物足りなさを感じてしまうかもしれないです。
 でも、紹介してもらった学生団体の方からも聞いていたようにとてもいい企業さんだったので、同企業さんが行っている『1dayインターン』だけでもぜひ参加してみてください。
 後日、その『1dayインターン』のことについてもブログに書くと思います。
 備忘録にもかかわらず読んでくださった方、拙い文章ですが、長らくお読みいただきありがとうございました。何かありましたら下記のtwitterでDMをくださると幸いです。


今回のインターン先の『tambourine(タンバリン)』さん

www.tam-bourine.co.jpwww.tam-bourine.co.jp

WANTEDLY

www.wantedly.com

3/26(木)にはオンライン説明会の開催を予定しているそうです。興味のある方は是非!!!!!!
オンライン説明会 | 人の役に立つ技術を追求したい新卒エンジニア大募集!

www.wantedly.com

今回の成果物

twitter.com

うp主

https://twitter.com/RandyGen0905

お、お、音楽!?

プロロロ―グ

 彌冨研 Advent Calendar 2019 9日目です。

 研究云々関係ないものですが、読んでもらえると嬉しいです。

 こういったものは初めてなので大目に見てくれるとなお...


 僕は曲を聞くのが好きで、移動時間や空き時間などによく邦楽を聞いています。

 好きすぎて、ドラムを始めちゃう並みには...

 皆さんは邦楽は聞きますか?

 そんな僕が音楽について、アドベントカレンダーを書かせてもらおうと思った時に、

 2019年 11月 18日 発行 ブシロード執行役員 中山淳雄さん著の オタク経済圏創世記 

という本をたまたま読んでいて、とても興味のそそられる面白い内容だったので、読んだ内容を交えて、今後の邦楽の発展に必要なものについて考察したいと思います。

紀伊国屋書店のリンクはこちらから

近年の音楽事情



 近年、スマホiPhoneの普及に伴ってCDの売り上げが芳しくない状況が続いています。それは、日本国内だけの話ではなく、国外のCDの売り上げも落ち込んでいて、国内に関してはCDの売り上げの減少に応じて、生産量も減っているのが現状です。

 故に、その曲に対する評価をCDの売り上げだけで判断することはもってのほかで、音楽アプリ内の曲のダウンロード回数、再生回数が1つの評価基準として影響を持つようになってきました。

 そんな中、CDの売り上げを維持しているアーティストの1つにAKBグループがあります。

 AKBグループの代表的存在のAKB48秋元康プロデュースの女性アイドルグループで、2005年東京・秋葉原を拠点として活動をしています。この‘いち’アイドルグループがなぜここまでCDの売り上げを伸ばしているかと言うと、他とは異なったCDの価値にあります。

CDの活用方法



 通常のアーティストであればCDを買ってプレイヤーやスマホに落とし込んだり、イベントでサインを貰ったりなどです。

 しかし、AKBグループは購入したCDを自分のファンのアイドルへの当選票に置き換わり、同グループ内のアイドルの競争にユーザーが参加するためのイベントチケットになります。つまり、CDを購入することで、ユーザーはファンとなったアイドルを目に見える形で応援するでき、そのアイドルの成長を見届けることとなるからです。

その結果、2010年から15年にかけて音楽ライブ市場が突如として3倍に膨れ、パッケージの落ち込みも止まり、7000億円規模の市場におちついた。

同時期の全世界のパッケージ市場は89億ドルから58億ドルへと35%減少した。ほぼ全ての国で3割以上も落ちていた市場が、日本では2割弱の減少に留まり、日本は世界一の音楽パッケージ市場になっている。

そして、2012年に約4200億円の音楽ソフト販売額を上げ、ついに米国市場すらも超えて世界一となった。

このように、コミュニティがアイドルの成長物語をストーリーとして消費しているのである。

   出典元: 中山淳雄 オタク経済圏創世記 第二章 2.5次元のライブコンテンツ創世記 より

 これらを踏まえると、音楽単体ではなくそこから他のコンテンツへ派生、展開していくことが今後人気を集める要素の1つとなると考えました。

 そんな要素を兼ね揃えていると思う音楽ジャンルについて考察しました。

 それはボーカロイド、通称ボカロ曲です。

ボカロってなんぞや?



 ボカロとは、ボーカロイドの略で、ヤマハが開発した音声合成技術、およびその応用製品の総称で、メロディーと歌詞を入力してキャラクターに歌を歌わせる音楽ソフトの名称のことを言います。

 大まかな仕組みは、歌手や声優の歌声から抽出したビブラートや音の断片などの音のデータを変換し、データベース化します。これを入力した歌詞や音程に合わせてデータから音声を作り出し歌声として出力する技術です。

 例えば、ボーカロイドを体感できるイベント『マジカルミライ』は、2013年より毎年開催しており、今年は大阪と東京の2会場で実施し、過去最大の約5万人を動員。累計動員数は21万人以上でした。

 そんなボカロ曲が今後も売れていくだろうと思われる理由は2つあります。
(今も売れているとは思いますが、さらにという意味で...)

注:以下の表現として、ボーカロイドに収録されている様々な歌手の「歌声ライブラリ」の1つとして「初音ミク」という商品がありますが、ここではそれらを総括して初音ミクと呼びます。

Why vocaloid



 1つ目は、その拡散する媒体にあると考えられます。

 初音ミクの主な活躍の場はインターネットです。

 ユーザーが初音ミクの「歌声ライブラリ」やキャラクターデザインを使って創作した曲や映像は、主にYouTubeニコニコ動画などの動画投稿サイトにアップされます。

 昨今の若者は情報源のみならず、余暇時間をPCやスマホを用いた娯楽にあてることが非常に多いです。このことによる若者のテレビ離れは進んでいます。

 それによって、SNSとの連動性が極めて高い動画投稿サイトを主な活躍の場としている初音ミクにマッチしており、話題が話題を生む形で拡散しています。

 これは、今のメディアの形が崩れない限り一番拡散される方法なので、今後も今以上に人気を集めていくだろうと考えられます。


 2つ目は、初音ミクというものの在り方にあると考えられます。


 初音ミクは、自分の作った曲を自分の思い通りに歌わせることができ、MVを作るにしても自分のイメージした初音ミクを描き、曲に合わせて踊らせたりすることもできます。

 実際に、MMD(Miku Miku Dance)のいうプリセットされたキャラクターの3Dモデルを操作しコンピュータアニメーションを作成する3DCGソフトウェアが存在します。

 このように、初音ミクはそのほとんどがユーザーによって形作られ、ユーザー次第で様々な楽しみ方があり、ユーザー同士で初音ミクを作り上げることができる、これらが初音ミクの特徴であり、今後の人気につながる要因の一つだと考えられます。

 また、多くの動画配信サイトの配信者によって配信される俗に言う『歌ってみた』動画初音ミクに歌わせていた曲を人が歌うことで、聞いている人に親近感や自分も歌ってみたいという欲望に駆られることも少なくないと思います。

 このような、二次創作の多さも自由度の高さからくるもので、先に述べた初音ミクの拡散媒体を考慮すると、インターネットに初音ミク初音ミクから派生したコンテンツであふれかえることが想像できます。

で?



 これらより、拡散する媒体や初音ミクというものの在り方によってユーザーの自発的かつ自由な情報拡散、自由度が高い創作活動が多くの曲を生み出し、広まる

 これがボーカロイド曲、ボカロ曲が今後人気を集めていくだろう要因であり、今後の邦楽の発展に必要な要因だと考えました。

 よって、曲単体ではなくそこから他のコンテンツへの聞いてる人の自発的かつ自由な情報拡散と自由度の高い派生、展開が今後の邦楽の発展に必要なものだと考えました。

君の名は?

 初めまして、B1のRandyGenです。慣れないこと多くてAdvent CalendarもNo nameのまま...

 彌冨研の先輩方にはいつもお世話になってます。感謝してもしきれないです。

 北田さんに(おそらく冗談半分?)『書いてみる?』と誘われたので書いてみました。

 投稿遅くなって申し訳ございません。また、機会があれば書かせてほしいです。

 彌冨研Advent Calendarはまだまだたくさん続くので、毎日投稿楽しみにしてます。

軽く自己紹介

 一浪のB1。高校で遊びまくった後悔をバネに日々精進。
 夏休み明けにUnityで2Dシューティングゲーム作成。
 今は、Pythonでクローリングした内容をTwitterbotに吐かせるものを作成中。
 その他もろもろ...
 春休みから初インターン。ちなみに、好きなバンドはGALNERYUS


 日々の冷え込みが激しくなってきました。皆様のますますのご健康をお祈りしております。