【Python, Docker, Github】初心者向けのTIP集~データサイエンス100本ノック~

datascience

The-Japan-DataScientist-SocietyのPython編を解き終わったので、回答する際に閲覧したサイトを忘備録として、まとめておきます。

1. データサイエンス100本ノック概要

以下、The-Japan-DataScientist-Societyから引用です。

  • データサイエンス100本ノックは構造化データ加工編である
  • 演習問題はSQL、Python、Rで共通
  • 言語によっては向かない設問もあるが、「この言語のときはこう書けば実現できる」という技術習得を目指すことを優先
  • 個人情報のように見える項目は全てダミーデータを利用
  • 大学、企業など組織でのご利用にあたっては、「データサイエンティスト協会スキル定義委員」の「データサイエンス100本ノック(構造化データ加工編)」を利用していることを明示いただければ自由に利用してOK
  • データサイエンス100本ノック(構造化データ加工編)の利用に関するご質問等について、個別での対応は受けかねますので予めご了承ください

演習問題を行うには、GitとDockerの準備が必要です。

以下では、引用したサイトと100本ノック内で関連のある問題に対し、私の回答を使用例として記載しています。

2. データフレーム関連

2-1. Query関数

最初はpandasでのスライスで対応していたが、途中から複数条件などを記載するのが楽だと気づき、Query関数を多用するようになりました。

pandas.DataFrameの列の値に対する条件に応じて行を抽出するにはquery()メソッドを使う。比較演算子や文字列メソッドを使った条件指定、複数条件の組み合わせなどをかなり簡潔に記述できて便利。

pandas.DataFrameの行を条件で抽出するquery
""" P-005: """
""" スライス """
df_receipt[["sales_ymd", "customer_id", "product_cd", "amount"]][(df_receipt['customer_id']=="CS018205000001")&(df_receipt['amount']>=1000)]
""" クエリ """
df_receipt[["sales_ymd", "customer_id", "product_cd", "amount"]].query("customer_id == 'CS018205000001' & amount >= 1000")

2-2. ソート

pandas.DataFrameやpandas.Seriesの値をソートするのに必要な関数。

100本ノックではよく使います。

pandas.DataFramepandas.Seriesをソート(並び替え)するには、sort_values()sort_index()メソッドを使う。昇順・降順を切り替えたり、複数列を基準にソートしたりできる。

pandas.DataFrame, Seriesをソートするsort_values, sort_index
""" P-17: """
df_customer.sort_values('birth_day', ascending=True).head(10)

""" P-18: """
df_customer.sort_values('birth_day', ascending=False).head(10)

2-3. ランク

ある値に対し、ランク付けしたいときに使用する関数。引数の設定方法でランク付け方法の変更が可能です。

pandas.DataFrameの行・列, panda.Seriesを順位付けするにはrank()メソッドを使う。

pandas.DataFrame, Seriesを順位付けするrank
""" P-19:  """
df0, df1 = df_receipt[['customer_id', 'amount']], df_receipt['amount'].rank(method='min', ascending=False)
df = pd.concat([df0, df1], axis=1)
df.columns = ['customer_id', 'amount', 'ranking']
df.sort_values('ranking', ascending=True).head(10)

2-4. グルーピング

pandasでのグルーピングでとてもお世話になる関数です。使いこなせれば、データ加工はかなりスムーズになりました。

pandas.DataFramepandas.Seriesgroupby()メソッドでデータをグルーピング(グループ分け)できる。グループごとにデータを集約して、それぞれの平均、最小値、最大値、合計などの統計量を算出したり、任意の関数で処理したりすることが可能。

pandas.DataFrameをGroupByでグルーピングし統計量を算出
""" P-023: """
df_receipt.groupby(df_receipt['store_cd']).agg({'amount':'sum', 'quantity':'sum'}).reset_index().head(10)

2-5. 重複行の抽出・削除

指定したコラムで重複する値がある行を抽出・削除するときに使用する関数。

使いこなせるとデータ処理が楽になりそう。

pandas.DataFramepandas.Seriesから重複した要素を含む行を検出・抽出するにはduplicated()、削除するにはdrop_duplicates()を使う。

pandas.DataFrame, Seriesの重複した行を抽出・削除
""" P-039: """
df_sum = df_receipt.groupby('customer_id').agg({'amount':'sum'}).reset_index().query('not customer_id.str.startswith("Z")', engine='python')
df_sum = df_sum.sort_values('amount', ascending=False).head(20)
df_date = df_receipt[~df_receipt.duplicated(subset=['customer_id', 'sales_ymd'])]
df_date = df_date.groupby('customer_id').agg({'sales_ymd':'count'}).reset_index().query('not customer_id.str.startswith("Z")', engine='python')
df_date = df_date.sort_values('sales_ymd', ascending=False).head(20)
pd.merge(df_sum, df_date, how='outer', on='customer_id')

""" P-070: """
df_date = pd.merge(df_receipt[['customer_id', 'sales_ymd']], df_customer[['customer_id', 'application_date']], how='inner', on='customer_id')
df_date = df_date.drop_duplicates()
df_date['sales_ymd'] = pd.to_datetime(df_date['sales_ymd'].astype(str))
df_date['application_date'] = pd.to_datetime(df_date['application_date'])
df_date['elasped_days'] = df_date['sales_ymd'] - df_date['application_date']
df_date.head(10)

2-6. Shift関数

知ることができてよかった思う関数の一つ。使いこなせれば、いろいろな表計算が自動でできると期待してます。

pandas.DataFramepandas.Seriesのデータを行または列方向にずらすにはshift()を使う。

pandasでデータを行・列(縦・横)方向にずらすshift

ついでにこちらも覚えておくと便利でした。

pandas.DataFramepandas.Seriesの行または列の差分・変化率を取得するにはdiff()pct_change()メソッドを使う。例えば一行前のデータとの差分・変化率を取得したりできる。

pandasで行・列の差分・変化率を取得するdiff, pct_change
""" P-042: """
df_date_amount = df_receipt.groupby('sales_ymd').agg({'amount':'sum'}).reset_index()
for i in range(1,4):
    if i == 1:
        df_date_lag = pd.concat([df_date_amount, df_date_amount.shift(i)], axis=1)
    else:
        df_date_lag = pd.concat([df_date_lag, df_date_amount.shift(i)], axis=1)
df_date_lag.columns = ['sales_ymd_1', 'amount_1', 'sales_ymd_2', 'amount_2','sales_ymd_3', 'amount_3','sales_ymd_4', 'amount_4']
df_date_lag.head(10) 

2-7. ピボットテーブル

エクセルでよく使うあれです。

pandas.pivot_table()関数を使うと、Excelなどの表計算ソフトのピボットテーブル機能と同様の処理が実現できる。

カテゴリデータ(カテゴリカルデータ、質的データ)のカテゴリごとにグルーピング(グループ分け)して量的データの統計量(平均、合計、最大、最小、標準偏差など)を確認・分析することが可能。便利。

pandasのピボットテーブルでカテゴリ毎の統計量などを算出
""" P-043: """
df_sales_summary = pd.merge(df_receipt, df_customer, how='inner', on='customer_id')
df_sales_summary['era'] = df_sales_summary['age'].apply(lambda x: int(x / 10) *10)
df_sales_summary_piv = pd.pivot_table(df_sales_summary, index='era', columns='gender_cd', values='amount', aggfunc=sum).reset_index()
df_sales_summary_piv.columns = ['era', 'male', 'female', 'unknown']
df_sales_summary_piv

2-8. Stack関数

データテーブルを並び替えたいときに使用する。

列方向に並んだデータを行方向に並べ替えたり、行方向に並んだデータを列方向に並べ替えたりして、データの構造を再形成できる。

pandasでstack, unstack, pivotを使ってデータを整形
""" P-043, 044: """
df_sales_summary = pd.merge(df_receipt, df_customer, how='inner', on='customer_id')
df_sales_summary['era'] = df_sales_summary['age'].apply(lambda x: int(x / 10) *10)
df_sales_summary_piv = pd.pivot_table(df_sales_summary, index='era', \
                       columns='gender_cd', values='amount', aggfunc=sum).reset_index()
df_sales_summary_piv.columns = ['era', 'male', 'female', 'unknown']

df_sales_summary_piv.set_index('era').stack().reset_index() \
.replace({'male': '00', 'female': '01', 'unknown': '99'}) \
.rename(columns={'level=1': 'gender_cd', 0: 'sum_amount'})

2-9. データフレーム結合

まずはmerge関数の使用法を抑えておけば問題ないと思います。

日付や名前などの共通のデータ列を持っている複数のpandas.DataFrameをその列の値に従って結合するにはpandas.merge()関数またはpandas.DataFramemerge()メソッドを使う。

インデックス列を基準にする場合はpandas.merge()関数も使えるし、pandas.DataFramejoin()メソッドも使える。

pandas.DataFrameを結合するmerge, join(列・インデックス基準)
""" P-036 """
pd.merge(df_receipt, df_store[['store_cd', 'store_name']], how='inner', on='store_cd').head(10)

2-10. データ読み書き(csv, tsvファイル)

csvの他にtsv処理もあるので、記載しておきます。

panda.DataFrameまたはpandas.Seriesのデータをcsvファイルとして書き出したり既存のcsvファイルに追記したりしたい場合は、to_csv()メソッドを使う。区切り文字を変更できるので、tsvファイル(タブ区切り)として保存することも可能。

pandasでcsvファイルの書き出し・追記(to_csv)

csvファイル、tsvファイルをpandas.DataFrameとして読み込むには、pandasの関数read_csv()read_table()を使う。

pandasでcsv/tsvファイル読み込み(read_csv, read_table)
""" P-094: """
df_tmp.to_csv('./data/df_category.csv', encoding='utf-8', index=False)

""" P-097: """
df_category_1 = pd.read_csv('./data/df_category.csv', encoding='utf-8')
df_category_1.head(10)

""" P-099: """
df_tmp.to_csv('./data/df_category_4.tsv', sep='\t', encoding='utf-8', header=False)

""" P-100: """
pd.read_table('./data/df_category_4.tsv', encoding='utf-8')

2-11. Replace関数

DataFrameの要素を変更したいときに使用する。

Frame全体でもコラムでも指定可能。辞書使用で複数指定も可能です。

pandas.DataFramepandas.Seriesの要素の値を置換するには、replace()メソッドを使う。

複数の異なる要素を一括で置き換えたり正規表現を使ったりすることもできる。

pandas.DataFrame, Seriesの要素の値を置換するreplace
""" P-44: """
df_sales_summary_piv.set_index('era').stack().reset_index() \
.replace({'male': '00', 'female': '01', 'unknown': '99'}) \
.rename(columns={'level=1': 'gender_cd', 0: 'sum_amount'})

2-12. 行列名の変更

よく使うけど、よく忘れる関数でした。何回も使用して覚えておいたほうが良いと思います。

pandas.DataFrameの行名(インデックス, index)・列名(カラム名, columns)を変更するには以下についてサンプルコードとともに説明する。任意の行名・列名を変更: rename()

pandas.DataFrameの行名・列名の変更
""" P-048: """
df_receipt_unix = pd.to_datetime(df_receipt['sales_epoch'], unit='s')
pd.concat([df_receipt_unix, df_receipt[['receipt_no', 'receipt_sub_no']]], axis=1)\ 
        .rename(columns={'sales_epoch': 'sales_ymd'}).head(10)

3. 欠損値処理関連

3-1. 欠損値要素の処理

データ解析には必須関数!

欠損値を除外(削除)するにはdropna()メソッド、欠損値を他の値に置換(穴埋め)するにはfillna()メソッドを使う。

pandasで欠損値NaNを除外(削除)・置換(穴埋め)・抽出
""" P-080: """
df_product_1 = df_product.dropna(how='any') #デフォルトany
len(df_product), len(df_product_1)

""" P-081: """
price_mean = round(df_product['unit_price'].mean())
cost_mean = round(df_product['unit_cost'].mean())
df_product_2 = df_product.fillna({'unit_price': price_mean, 'unit_cost': cost_mean})

3-2. 欠損値を含む計算

データには欠損値があることも多く、欠損値を無視して計算することもあります。

その際に使用する関数です。

NumPyの配列ndarrayにひとつでも欠損値np.nanが含まれている場合、通常の関数sum()を使うと、np.nanが返される。nansum()を用いることで、欠損値np.nanを除外した値の合計が算出できる。

NumPyで欠損値np.nanを含む配列ndarrayの合計や平均を算出

3-3. 欠損値個数

欠損値の有無および欠損値の個数をカウントすることはデータ前処理で必須ですね。

pandas.DataFramepandas.Seriesに欠損値NaNが含まれているどうかを判定する方法、および、欠損値NaNの個数をカウントする方法を説明する。

pandasで欠損値NaNが含まれているか判定、個数をカウント
""" P-079: """
df_product.isnull().sum()

4. 時間型関連

4-1. 時間型変更

pandasで時間型を扱う際には必須の関数です。

pandas.DataFrameの日時(日付・時間)を表した列を操作する方法を説明する。文字列とdatetime64[ns]型との相互変換、年月日、時刻を数値として抽出する方法など。

pandas.DataFrameの(日時日付・時間)を表した列を操作する方法を説明する。文字列とdatetime64[ns]型との相互変換、年月日、時刻を数値として抽出する方法など。

pandasで日付・時間の列を処理(文字列変換、年月日抽出など)

某計測ツールから落としてきたCSVファイルを pandas で読み込んで、日時情報がUNIX時間だったので、pandas で datetime に変換した備忘録。

pandasでunix時間をdatetimeに変換する
""" P-050:  """
df_receipt_unix = pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.strftime('%m') #.month
pd.concat([df_receipt_unix, df_receipt[['receipt_no', 'receipt_sub_no']]], axis=1).rename(columns={'sales_epoch': 'sales_m'}).head(10)

4-2. 日付加算減算

この関数を覚えておくと、日付計算が楽になりました。

標準ライブラリではありませんが、日付処理のデファクトスタンダードともいえる python-dateutil モジュールを使用することで月の加算ができる relativedeltaが使えます。

日付に月単位で加算減算する (relativedelta)
""" P-074:  """
df_date = df_receipt[['customer_id', 'sales_ymd']]
df_date = df_date.drop_duplicates()
df_date['sales_ymd'] = pd.to_datetime(df_date['sales_ymd'].astype(str), format='%Y%m%d')
df_date['monday'] = df_date['sales_ymd'].apply(lambda x: x - relativedelta(days=x.weekday()))
df_date['elasped_days'] = df_date['sales_ymd'] - df_date['monday']
df_date.head(10)

5. データ分析関連

5-1. ユニーク要素処理

ユニークな要素に対する処理を行いときに使用します。新たなコラムや、除外したい行などにも利用できます。

pandas.DataFrameの列、pandas.Seriesにおいて、ユニークな要素の個数(重複を除いた件数)、及び、それぞれの要素の頻度(出現回数)を取得する方法を説明する。

pandas.Seriesのメソッドunique()value_counts()nunique()を使う。nunique()pandas.DataFrameのメソッドとしても用意されている。

pandasでユニークな要素の個数、頻度(出現回数)をカウント
""" P-053: """
df_post = df_customer[['customer_id', 'postal_cd']].copy()
df_post['postal_flag'] = df_post['postal_cd'].apply(lambda x: 1 if int(x[:3]) <= 209 else 0)
pd.merge(df_receipt, df_post, how='inner', on='customer_id').groupby('postal_flag').agg({'customer_id': 'nunique'})

5-2. apply, map関数

データ処理では、各要素に関数や、分類処理などを行うことが多いです。map関数やapply関数は、使用方法を覚えておくととても役立ちます。

pandasのオブジェクト(pandas.DataFramepandas.Series)に関数を適用する場合、どんな関数を適用するか、要素・行・列のいずれに適用するかによって、使うメソッドなどが異なる。

pandasで要素、行、列に関数を適用するmap, applymap, apply
""" P-055:  """
df_amount_sales = df_receipt[['customer_id', 'amount']].groupby('customer_id').agg({'amount': 'sum'}).reset_index()
df_quan_25 = np.quantile(df_amount_sales['amount'], 0.25)
df_quan_50 = np.quantile(df_amount_sales['amount'], 0.5)
df_quan_75 = np.quantile(df_amount_sales['amount'], 0.75)

def calc_quan(x):
    if x <= df_quan_25:
        return 1
    elif  df_quan_25 < x <= df_quan_50:
        return 2
    elif  df_quan_50 < x <= df_quan_75:
        return 3
    elif x > df_quan_75:
        return 4
    
df_amount_sales['amount_cat'] = df_amount_sales['amount'].apply(lambda x: calc_quan(x))
df_amount_sales.head(10)

5-3. パーセンタイル

統計学で四分位数を使用する場合は、よく使用します。

pandas.DataFramepandas.Seriesの分位数・パーセンタイルを取得するにはquantile()メソッドを使う。

pandasで分位数・パーセンタイルを取得するquantile
""" P-055:  """
df_amount_sales = df_receipt[['customer_id', 'amount']].groupby('customer_id').agg({'amount': 'sum'}).reset_index()
df_quan_25 = np.quantile(df_amount_sales['amount'], 0.25)
df_quan_50 = np.quantile(df_amount_sales['amount'], 0.5)
df_quan_75 = np.quantile(df_amount_sales['amount'], 0.75)

def calc_quan(x):
    if x <= df_quan_25:
        return 1
    elif  df_quan_25 < x <= df_quan_50:
        return 2
    elif  df_quan_50 < x <= df_quan_75:
        return 3
    elif x > df_quan_75:
        return 4
    
df_amount_sales['amount_cat'] = df_amount_sales['amount'].apply(lambda x: calc_quan(x))
df_amount_sales.head(10)

5-4. ダミー変数

機械学習などでダミー変数を用意するときは必須の関数ですね。

pandasでカテゴリ変数(カテゴリカルデータ、質的データ)をダミー変数に変換するには、pandas.get_dummies()関数を使う。

文字列でカテゴリー分けされた性別などのデータを、男を0, 女を1のように変換したり、多クラスの特徴量をone-hot表現に変換したりすることができる。機械学習の前処理として行うことが多い。

pandasでカテゴリ変数をダミー変数に変換(get_dummies)
""" P-058: """
pd.get_dummies(df_customer[['gender_cd', 'customer_id']], columns=['gender_cd'], prefix_sep='_').head(10)

5-5. 正規化・標準化

こちらも機械学習の前処理などで行う正規化・標準化です。

Pythonのリスト(list型)、NumPy配列(numpy.ndarray)、および、pandas.DataFrameを正規化・標準化する方法について説明する。

Python標準ライブラリやNumPy、pandasのメソッドを利用して最大値や最大値、平均、標準偏差を求めて処理することも可能だが、SciPyやscikit-learnでは正規化・標準化のための専用の関数やクラスが用意されている。

Pythonで正規化・標準化(リスト、NumPy配列、pandas.DataFrame)
""" P-059: """
df_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').agg({'amount': 'sum'}).reset_index()
df_amount['amount_scale'] = preprocessing.scale(df_amount['amount'])
df_amount.head(10)

5-6. 指数・対数計算

データ処理で多用する指数・対数の関数です。

Pythonの数学関数の標準モジュールmathを使うと、指数関数および対数関数(自然対数、常用対数、二進対数)の計算ができる。

Pythonで指数関数・対数関数を計算(exp, log, log10, log2)
""" P-061:"""
df_sales_amount = df_receipt[['amount', 'customer_id']].query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').agg({'amount': 'sum'}).reset_index()
df_sales_amount['amount_log10'] = df_sales_amount['amount'].apply(lambda x: math.log10(x+1))
df_sales_amount.head(10)

""" P-062: """
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').agg({'amount': 'sum'}).reset_index()
df_sales_amount['amount_loge'] = df_sales_amount['amount'].apply(lambda x: math.log(x+1))
df_sales_amount.head(10)

5-7. 小数点処理

処理方法で選択する関数が異なる点に注意します。

Pythonで浮動小数点数floatの小数点以下を切り捨て・切り上げするには、標準ライブラリmathモジュールのfloor()ceil()を使う。

Pythonで小数点以下を切り捨て・切り上げ: math.floor(), math.ceil()

Pythonで数値(浮動小数点float型または整数int型)を四捨五入や偶数への丸めで丸める方法について、以下の内容を説明する。

Pythonで小数・整数を四捨五入するroundとDecimal.quantize
 """ P-065  """
df_new_product = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_new_product['new_price'] = df_new_product['unit_cost'].apply(lambda x: np.floor(x/0.7))
df_new_product['new_ratio'] = (df_new_product['new_price'] - df_new_product['unit_cost'])/df_profit['unit_price']
df_new_product.head(10)

 """ P-066  """
df_new_product = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_new_product['new_price'] = df_new_product['unit_cost'].apply(lambda x: np.round(x/0.7))
df_new_product['new_ratio'] = (df_new_product['new_price'] - df_new_product['unit_cost'])/df_profit['unit_price']
df_new_product.head(10)

 """ P-067  """
df_new_product = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_new_product['new_price'] = df_new_product['unit_cost'].apply(lambda x: np.ceil(x/0.7))
df_new_product['new_ratio'] = (df_new_product['new_price'] - df_new_product['unit_cost'])/df_profit['unit_price']
df_new_product.head(10)

5-8. ランダムサンプリング

統計学などで母集団から標本を取るときなどに使えそうです。

行数の多いpandas.DataFramepandas.Seriesのデータを確認するときに、行または列をランダムに抽出(ランダムサンプリング)するメソッドsample()が便利。

なお、大きいサイズのpandas.DataFramepandas.Seriesのデータを確認するときに使えるほかのメソッドとして、先頭・末尾の行を返すhead()tail()もある。

pandasの行・列をランダムサンプリング(抽出)するsample
 """ P-075:  """
df_customer.sample(frac=0.01).groupby("gender_cd").agg({"customer_id":"count"})

5-9. 外れ値除去

外れ値を確認して、除去する手法はデータ分析で必須ですね。

データ解析を行う場合、外れ値を含んでいると相関係数などを求めたときにおかしな値が得られてしまうことがあります。今回はこの外れ値を含む行を取り除く関数を、pandasのdataframeを使用して紹介します。

pandasのデータフレームから外れ値を含む行を取り除く
 """ P-077:  """
df_tmp = df_receipt[~df_receipt.customer_id.str.startswith("Z")].groupby("customer_id").amount.sum()
mean = df_tmp.mean()
std = df_tmp.std(ddof=0)
df_tmp[abs(mean-tmp) >= 3*std].reset_index().head(10)

5-10. 正規表現

正規表現はPython公式サイトを閲覧できれば良いと思います。

とりあえず覚えるよりも、コードを書き続けるほうが良さそう。

このモジュールは Perl に見られる正規表現マッチング操作と同様のものを提供します。

正規表現操作
 """ P-013:  """
df_customer.query("status_cd.str.contains('^[A-F]')", engine='python').head(10)

 """ P-014:  """
df_customer.query("status_cd.str.contains('[1-9]$')", engine='python').head(10)

 """ P-015:  """
df_store.query("tel_no.str.contains('^[0-9]{3}-[0-9]{3}-[0-9]{4}$')", engine='python')

以上です。お疲れさまでした!

合わせてSQL編もどうぞ御覧ください。

datascience

1件のコメント

【SQL, Docker, Github】初心者向けのTIP集~データサイエンス100本ノック~ へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

ABOUT US

Baran-gizagiza
経歴:浪人→理系大学院卒業→大手製造業に就職(技術職)→アメリカ赴任中 仕事は、研究・設計など上流工程の仕事に携わっています。企業勤務を継続しながら、新しいことにチャレンジしたいと思い、ブログを下記はじめました。 このブログでは、趣味である 筋トレ(健康、ダイエット) AIとデータ(高校数学、プログラミング) 読書(主に自己啓発系) を中心に、人生経験やおすすめ情報の発信しています。