SENSY Product Dev Tech Blog

SENSYプロダクト開発チームのTechBlogです。

データ分析のためのシェルワンライナー入門3(CSVファイルの列ずれを検出する)

こんにちは、データサイエンス部の荻です。

はじめに

データサイエンス部で隔週の社内勉強会を行っており、その内容を共有します。 今日は、データの受領・前処理・納品などで時短になる、簡単なシェルコマンドで解決する方法を紹介します。 前回の記事の続きになります。

今回の課題

「顧客からデータを受け取る際、データが列づれしていた」 まずは、列づれしていないデータと列づれしているデータに分けたいですよね。 そこで、シェルコマンドを使用して、これらのデータを分ける処理を書きます。

コマンド

# 1. 列ずれを検出し、異常データを抽出し、別のcsvとして保存
awk -F ',' 'NR==1 {n=NF; print; next} NF!=n {if (!printed_header) {printed_header=1} print}' base_data.csv > abnormal_data.csv
# 2. 正常なデータを抽出する
awk -F ',' 'NR==FNR {if (NR==1) {n=NF} else if (NF!=n) {skip[NR]=1} next} !(FNR in skip)' base_data.csv base_data.csv > normal_data.csv

処理の内容

一行目にヘッダー情報が入っているCSVファイルに対して、#1は、1行目の列数と同じ列数ではないデータは出力するコマンド、#2は、1行目の列数と同じ列数のデータを出力するコマンドになっています。

詳しい解説

2 つコマンドの共通部分

両方のコマンドは awk を使用しており、CSV ファイルの処理を行っています。共通の要素は以下の通りです:

  1. -F ',': カンマをフィールド区切り文字として指定しています。これにより、CSV ファイルの各列を正しく識別できます.
  2. NR==1 {...}: 最初の行(通常はヘッダー行)に対して特別な処理を行っています。この部分で、正常なデータの列数を変数 n に保存しています.
  3. NF!=n: この条件を使用して、各行の列数が最初の行(ヘッダー)と一致するかどうかをチェックしています。これにより、列ずれを検出しています.
  4. base_data.csv: 両方のコマンドで同じ入力ファイルを使用しています。
  5. 出力のリダイレクト(>): 処理結果を新しい CSV ファイルに保存しています。

1. 列ずれを検出し、異常データを抽出

awk -F ',' 'NR==1 {n=NF; print; next} NF!=n {if (!printed_header) {printed_header=1} print}' base_data.csv > abnormal_data.csv

1. NR==1 {n=NF; print; next}:

  • 最初の行(ヘッダー)の列数をnに保存します.
  • ヘッダー行を出力し、次の行に進みます.

2. NF!=n: 現在の行の列数がnと異なる場合に実行します.

3. if (!printed_header) {printed_header=1}:

  • 異常データのヘッダーが未出力の場合、フラグを設定します.

4. print: 列数が異なる行(異常データ)を出力します.

5. 結果をabnormal_data.csvに保存します.

2. 正常なデータを抽出する

awk -F ',' 'NR==FNR {if (NR==1) {n=NF} else if (NF!=n) {skip[NR]=1} next} !(FNR in skip)' base_data.csv base_data.csv > normal_data.csv

1. NR==FNR: 最初のファイル処理時に実行されるブロックです.

2. if (NR==1) {n=NF}: 最初の行の列数をnに保存します.

3. else if (NF!=n) {skip[NR]=1}:

  • 列数がnと異なる行の行番号をskip配列に記録します.

4. next: 次の行に進みます.

5. !(FNR in skip):

  • 2 回目のファイル処理時、skip配列に含まれない行を出力します.

6. 結果をnormal_data.csvに保存します.

使い方

1. csv ファイルが格納されているディレクトリに移動:

csv ファイルが保存されているディレクトリに移動してください。

2. コマンドの実行:

以下のコマンドを実行します。

# 1. 列ずれを検出し、異常データを抽出し、別のcsvとして保存
awk -F ',' 'NR==1 {n=NF; print; next} NF!=n {if (!printed_header) {printed_header=1} print}' base_data.csv > abnormal_data.csv
# 2. 正常なデータを抽出する
awk -F ',' 'NR==FNR {if (NR==1) {n=NF} else if (NF!=n) {skip[NR]=1} next} !(FNR in skip)' base_data.csv base_data.csv > normal_data.csv

注意点

  • このコマンドを実行する前に、必ずデータのバックアップを取ってください。

おわりに

次回は、列づれが起きているデータの修正を行います。

今回使用したデータ

base_data.csv

id product_name price
1 nezjxhjjlx 915
2 ohzcqyzeyx 999
3 depmzyrkro 963
4 zuzvdemlox 1 66
5 xccgxtkwwu 941
6 scqsrrkeye 1 10
7 afdmkqorzd 1 69
8 pkgbulxghk 1 68
9 lvhxcyaooe 1 85
10 jhkyfwgoyd 1 77
11 akghzvgwda 1 13
12 agzqyusozz 1 44
13 nhuvkkgqmc 1 58
14 rnaeuodqnj 1 71
15 alacczwmwu 952
16 lbyuegldfc 969
17 pxmnyhrnum 1 64
18 uzqljcnzmu 984
19 uejpchivil 1 94
20 oewfafsnhn 1 48
21 fflpkzlfzd 901
22 llxgmdimzo 908
23 qxxcabnqgl 1 29
24 fbunvaiend 922
25 pxunapudfi 942
26 xrheuqdkke 1 35
27 dghxopeoay 937
28 przprezgtd 934
29 wnjdrwblbm 904
30 einujjrujy 982
31 mvsstncjml 1 22
32 guuekhqgqh 1 92
33 rapizfkyab 1 10
34 qgcuepyfqw 1 45
35 wjmrzwmssz 1 75
36 aqsrrbvedt 997
37 wcsfcbrvrh 963
38 hmiydkcxul 960
39 dggzwnurfb 1 6
40 ebnscvlmuw 945
41 ofkybiahfq 993
42 qtbqsfdiyb 971
43 jugvjcirzp 1 37
44 fhddgugzgr 1 33
45 akvwtmnpsy 985
46 acvbbkzcpn 982
47 otkbkgebfr 1 46
48 opyaexdvda 1 77
49 joquydguxl 1 96
50 attynpacfd 904
51 gunxrmgrln 1 24
52 bkyduicufz 1 17
53 recmqqzjbw 972
54 mdavzhxmap 1 63
55 qfutefnivi 1 77
56 cjjacgwdvn 1 39
57 zszejxkshx 921
58 pscdwcolom 1 93
59 ulhbkncwgm 941
60 vwyspqyhlf 1 33
61 qbcffhqxrg 906
62 vrterspkpy 956
63 fmprdcspiy 955
64 bkfpditgce 1 80
65 viigmvmpoi 1 25
66 gmckymhvli 1 19
67 zhvojwuraz 901
68 yplkhncrxn 1 87
69 qbzmqqtrvn 984
70 jgaagvebzj 947
71 aewebeyjsk 953
72 acqcumagvs 1 66
73 jstovobqxz 1 77
74 ntkwylrgww 967
75 eyqlddqsnn 997
76 swkkcrievy 1 24
77 gzthacvioz 957
78 lfagwfilqt 945
79 suqwuwjktu 963
80 icdtjplyuj 988
81 suxmljyorj 965
82 fmbikviggc 1 67
83 vwyixummdv 1 55
84 szbghhwzyo 1 1
85 dvhjcfsfkf 957
86 qgurculsye 1 70
87 wubdhfgudf 963
88 xqfdbrbmdf 972
89 arztqlkeky 1 55
90 wgncctzven 985
91 utvvpdsfwl 979
92 fkgjwhzixk 1 54
93 ckzdftoqys 1 47
94 ylgnrpcrdv 1 94
95 hpzpxyevsk 1 10
96 hxpxliqzwl 900
97 oigmbbqyyr 1 66
98 xfgojcmugs 1 95
99 oefkvqukto 931
100 xlnjwqpniw 1 38

データ分析のためのシェルワンライナー入門2(大量のエクセルファイルを全連結して、csvで吐く)

こんにちは、データサイエンス部の荻です。

はじめに

データサイエンス部で隔週の社内勉強会を行っており、その内容を共有します。 今日は、データの受領・前処理・納品などで時短になる、簡単なシェルコマンドで解決する方法を紹介します。 前回の記事の続きになります。

データ分析のためのシェルワンライナー入門1(カラム名が同じCSVファイル複数のファイルを一つのファイルにまとめる) - SENSY Product Dev Tech Blog

今回の課題

「顧客からデータを受け取る際、データが大量でExcelファイルが送られてきた」 必要なデータを抽出するのに、ファイルを個別に処理するのは労力が大きので、一つのファイルにまとめたいですよね。 そこで、シェルコマンドを使用して、これらのファイルを一括でCSVに変換し、さらに1つのファイルにまとめる方法をご紹介します。

コマンド

for file in *.xlsx; do xlsx2csv "$file" "${file%.xlsx}_sheet" --all; done && (head -n 1 $(find *_sheet -name "*.csv" | head -n 1) && find *_sheet -name "*.csv" | xargs tail -q -n +2) > combined.csv

解説

1. ExcelファイルのCSV変換

for file in *.xlsx; do xlsx2csv "$file" "${file%.xlsx}_sheet" --all; done
  • 目的: 現在のディレクトリ内のすべての.xlsxファイルを.csvファイルに変換します。
  • 動作:
    • 現在のディレクトリ内のすべての.xlsxファイルに対して処理を行います。
    • ブック内に複数のシートが存在しても、シートごとに処理をします。
    • .xlsxファイルを対応する.csvファイルに変換します。

2. CSVファイルの結合

(head -n 1 $(find *_sheet -name "*.csv" | head -n 1) && find *_sheet -name "*.csv" | xargs tail -q -n +2) > combined.csv
  • 目的: 生成されたすべての.csvファイルを1つのファイルに結合します。
  • 動作:
    • head -n 1 $(find *_sheet -name "*.csv" | head -n 1) : 最初に見つかったCSVファイルの1行目(ヘッダー)を取得します。
    • find *_sheet -name "*.csv" | xargs tail -q -n +2 : すべてのCSVファイルの2行目以降(データ部分)を取得します。
    • これらの出力を結合し、combined.csvという1つのファイルに保存します。

使い方

1. 必要なツールのインストール:

まず、xlsx2csvコマンドを使用するために、以下のコマンドでインストールします。

sudo apt-get install xlsx2csv

2. Excelファイルが格納されているディレクトリに移動:

Excelファイルが保存されているディレクトリに移動してください。

3. コマンドの実行:

以下のコマンドを実行します。

for file in *.xlsx; do xlsx2csv "$file" "${file%.xlsx}_sheet" --all; done && (head -n 1 $(find *_sheet -name "*.csv" | head -n 1) && find *_sheet -name "*.csv" | xargs tail -q -n +2) > combined.csv

注意点

  • このコマンドを実行する前に、必ずデータのバックアップを取ってください。
  • xlsx2csvコマンドがインストールされていることを確認してください。
  • Excelファイルの構造によっては、正確に変換されない場合があります。変換後のデータを必ず確認してください。
  • Excelに複数のシートが入っていても問題なく結合されます。
  • ファイルの内容に日本語が含まれたら文字化けはしないことを検証しましたが、特殊文字など細かい検証は行なっていないです。
  • Excelのシートのヘッダーが全て共通であることが条件です。
  • excelからcsvの中間ファイルのcsvは残しています。

データ分析のためのシェルワンライナー入門1(カラム名が同じCSVファイル複数のファイルを一つのファイルにまとめる)

こんにちは、データサイエンス部の荻です。

はじめに

データサイエンス部で隔週の社内勉強会を行っており、その内容を共有します。
今日は、データの受領・前処理・納品などで時短になる、簡単なシェルコマンドで解決する方法を紹介します。

今回の課題

「顧客からデータを受け取る際、データが大量で10〜20ファイルに分割されたCSVファイルが送られてきた」

これらのデータを処理する際に、Jupyterで開くにしても、RDBに登録するにしても、データを一つのファイルにまとめたいですよね。
そこで、コピペで完了するシェルワンライナーを作成しましたので、その活用方法と使用方法をご紹介します。

コマンド

awk 'FNR==1 && NR!=1 {next} {print}' *.csv > combined.csv

使い方

  1. 受領データのディレクトリに移動してください。
  2. 以上のコマンドを実行します。
  3. combined.csvという連結ファイルが作成されます。 注意: CSVファイルのカラムが全て共通であることが条件です。

解説

  1. awkコマンド:

    • awkは、テキストファイルの情報を抽出し、加工するためのコマンドです。ファイルの一行一行に対して処理を実行します。
  2. FNR==1 && NR!=1 {next}:

    • FNRは、各ファイル内の行番号を示します。最初のファイルではFNRNRは同じですが、2番目以降のファイルではFNRは1から再開します。
    • NRは、全体の行番号を示します。最初のファイルの最初の行ではNRも1です。
    • FNR==1 && NR!=1は、各ファイルの最初の行(ヘッダ行)をチェックしますが、最初のファイルのヘッダ行(NR==1)は除外します。つまり、2番目以降のファイルのヘッダ行はスキップされます。
    • {next}は、条件が真の場合に次の行に進むことを指示します。つまり、2番目以降のファイルのヘッダ行は出力されません。
  3. {print}:

    • これは、他の条件に当てはまらない行(つまり、最初のファイルのヘッダ行と、2番目以降のファイルのデータ行)を出力することを指示します。
  4. *.csv > combined.csv:

    • *.csvは、現在のディレクトリにあるすべてのCSVファイルを対象にします。
    • > combined.csvは、結果をcombined.csvというファイルに保存します。

補足

  • シェルワンライナー: シェルで一行のコマンドで複雑な処理を実現する技法のことです。

データ分析で汎用的に使えるPython入門2:プロット&バーグラフの描写

SENSYデータサイエンティストチーム井上です。

今回はデータ分析で汎用的に使えるPythonの記事(第2回目)になります。 EDA(Explanatory Data Analysis, 探索的データ分析)の際、よく確認する内容を関数化しておくことで作業を効率化するシリーズになります!!

今回の目的

要点

  • プロット&バーグラフの描写を出力する汎用的な関数を作成し、作業を効率化する

今回の関数で作成されるもの

  • プロット&バーグラフ
    • 左縦軸:プロットグラフ描写したい項目
    • 右縦軸:バーグラフ描写したい項目
    • 横 軸:時間

今回の目的の関数

def plot_and_bar_graph(df, time_column_name, plot_column_name, bar_column_name, plot_color="blue", bar_color="orange", title="", figsize=(10,6)):
    """
    プロット&バーグラフの描写

    Args:
        df (pd.DataFrame): 入力のデータフレーム
        time_column_name (str): 時間のカラム名
        plot_column_name (str): プロットグラフ描写したいカラム名
        bar_column_name (str): バーグラフ描写したいカラム名
        plot_color (str): プロットグラフ描写の色
        bar_color (str): バーグラフ描写の色
        title (str): グラフのタイトル
        figsize (tuple): 図のサイズ
    """
    # グラフの描画
    fig, ax1 = plt.subplots(figsize=figsize)

    # プロットグラフ(左Y軸)
    ax1.plot(df[time_column_name], df[plot_column_name], color=plot_color, label=plot_column_name, marker="o")
    ax1.set_xlabel(time_column_name)
    ax1.set_ylabel(plot_column_name, color=plot_color)
    ax1.tick_params(axis="y", labelcolor=plot_color)

    # バーグラフ(右Y軸)
    ax2 = ax1.twinx()
    ax2.bar(df[time_column_name], df[bar_column_name], color=bar_color, alpha=0.3, label=bar_column_name, width=20)
    ax2.set_ylabel(bar_column_name, color=bar_color)
    ax2.tick_params(axis="y", labelcolor=bar_color)

    # タイトルの設定
    plt.title(title)

    # 凡例
    fig.tight_layout()
    ax1.legend(loc="upper left")
    ax2.legend(loc="upper right")

    # グラフの表示
    plt.show()

実行例

手順1:必要なライブラリのインポート

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

手順2:サンプルデータセットの作成

  • 以下のコードでサンプルデータを作成できます!
    • 今回の内容は、小売を想定したサンプルデータセットになります。
      • order_date:販売日
      • store_id:店舗ID
      • product_id:商品ID
      • product_price:販売価格
      • quantity:販売数量
      • total_sales:合計販売金額
# パラメータ設定
np.random.seed(42)
dates = pd.date_range(start="2022-01-01", end="2024-12-31", freq="D")
store_ids = range(1, 6)  # 店舗ID 1~5
product_ids = range(101, 111)  # 商品ID 101~110

# 商品ごとの販売価格をランダムに設定(100~500円)
product_prices = {pid: np.random.randint(100, 501) for pid in product_ids}

# データ生成
data = []
for date in dates:
    for store_id in store_ids:
        for product_id in product_ids:
            quantity = np.random.randint(0, 21)  # 販売数量(0~20)
            if quantity > 0:  # 売れた場合のみ記録
                data.append([date, store_id, product_id, product_prices[product_id], quantity, product_prices[product_id] * quantity])

手順3:月間合計販売金額、月間合計販売数量の算出

  • 今回は横軸:年月、プロット:(月間合計販売金額)、バー:(月間合計販売数量)を描写していきたいと思います。
    • 集計対象カラム
      • order_date:販売日
      • total_sales:販売金額
      • quantity:販売数量
  • そのため、pandasで以下のように集計し、グラフ描写用のデータフレームを作成する。
# 年月に変換
df["year_month"] = df["order_date"].dt.to_period("M")

# 年月単位で販売合計金額と販売合計数量を集計
df_monthly_summary = df.groupby("year_month").agg(
    total_sales=("total_sales", "sum"),
    total_quantity=("quantity", "sum")
).reset_index()

# 'year_month'列をPeriod型からdatetime型に変換
df_monthly_summary['year_month'] = df_monthly_summary['year_month'].dt.to_timestamp()

手順4:今回の目的の関数の定義

def plot_and_bar_graph(...)

手順5:プロット&バーグラフの描写

plot_and_bar_graph(
    df=df_monthly_summary,
    time_column_name="year_month",
    plot_column_name="total_sales",
    bar_column_name="total_quantity",
    plot_color="blue",
    bar_color="orange",
    title="Monthly Sales and Quantity",
    figsize=(10, 6)
)

皆さんの分析の効率化の一助になれば幸いです!!

【GeoPandas】表形式データから地図上に情報を可視化する方法

実店舗系のデータ解析に携わっているPDチーム/データサイエンティストの川上雄大です!

以前執筆した記事で,業務で利用した全国道路・街路交通情勢調査一般交通量調査(道路交通センサス:RTC)情報を国土交通省データプラットフォームAPIで取得する方法を紹介ました.RTCデータは緯度経度に合わせて24時間自動車類交通量合計(metadata.RTC:24jikan_jidousharui_koutsuuryou_jouge_goukei_goukei)のような道路交通量を代表するような値を取得できます.以下の図に東京都に関して取得したRTCデータを示します.

この表からわかる通り,表形式データでは,土地的な情報を理解することは難しいです.

そこで,本記事では,GeoPandasを使用して,道路交通センサス情報を整理し,地図上に可視化する方法を解説します.GeoPandasを使用することで,地理情報データを扱うための機能を提供し,地理情報データの可視化や解析を行う際に便利です.

特に今回は,表形式データで示された緯度経度上の交通量を地図上のヒートマップで可視化する例を紹介します!

本記事では,GeoPandasの説明,データ処理方法の順で解説を行います.

1. はじめに

まず,本節では,GeoPandasの概要について説明します.

GeoPandas とは

GeoPandasは,Pythonでジオスペーシャルデータを扱うためのライブラリです.GeoPandasは,pandasのデータフレームを拡張し,ジオメトリデータを扱うための機能を提供します.GeoPandasを使用すると,地理情報システム(GIS)のような専用のツールを使用せずに,Pythonでジオスペーシャルデータを簡単に操作できます.

GeoPandasは,地理情報データを扱うための機能を提供するため,地理情報データの可視化や解析を行う際に便利です.

2. データ処理方法

本節では,GeoPandasを使用して,取得した道路交通センサス情報を整理し,地図上に可視化する方法を解説します.

2.1. GeoPandasのインストール

まずはじめに,GeoPandasに関連するライブラリをインストールします.

pip install geopandas contextily

2.2. データの変換

まずは以前執筆した記事を参考に,東京都の2021年度のRTCデータを取得します.ここでは,DPF:prefecture_code13(東京都)に設定して,東京都のRTCデータを取得します.前回の記事と重複するため,詳細は省略しますが,df_rtcという変数にDataFrame形式でデータを取得します.

まずは,GeoPandasのデータフレームに変換します.

import geopandas as gpd
from shapely.geometry import Point

# 地理データフレームに変換
geometry = [Point(xy) for xy in zip(df_rtc['metadata.DPF:longitude'], df_rtc['metadata.DPF:latitude'])]
gdf = gpd.GeoDataFrame(
    df_rtc[[
        "metadata.RTC:24jikan_jidousharui_koutsuuryou_jouge_goukei_goukei",  # 24時間自動車類交通量(上下合計) 合計(台)
    ]], 
    geometry=geometry, 
    crs='EPSG:4326',
)
gdf = gdf.to_crs(epsg=3099)

gdf

一見すると,通常のDataFrameと同じように見えますが,次のようなGeoDataFrameになっています.

ここで,緯度経度をPoint型に変換し,GeoDataFrameに変換しています.また,crs='EPSG:4326'で緯度経度を示す座標系を指定し,to_crs(epsg=3099)で日本の平面直角座標系に変換しています.平面座標系に変換することで,地図上にデータをプロットする際に,正確な距離関係の下で位置にプロットすることができます.

今回は,このままだとデータ数が多すぎるため,交通量が50000以上の主要道路のみを抽出します.

gdf_major = gdf[gdf['metadata.RTC:24jikan_jidousharui_koutsuuryou_jouge_goukei_goukei'] > 50000]

2.3. 地図上に可視化

最後に,地図上にデーカを可視化します.

import contextily as ctx
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors


# カラーマップの設定
cmap = plt.get_cmap('coolwarm')

# プロットの作成
fig, ax = plt.subplots(figsize=(10, 10))

gdf_major.plot(
    ax=ax,
    column='metadata.RTC:24jikan_jidousharui_koutsuuryou_jouge_goukei_goukei',
    cmap=cmap,
    markersize=20,
    marker='o',
    legend=False,
    alpha=0.8,
)

# カラーバーの追加
sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_array(gdf_major['metadata.RTC:24jikan_jidousharui_koutsuuryou_jouge_goukei_goukei'])
cbar = plt.colorbar(sm, ax=ax)
cbar.set_label('Traffic Volume')

# 背景地図を追加
ctx.add_basemap(ax, crs=gdf.crs.to_string(), alpha=0.4)

# タイトルとラベルの設定
plt.xlabel('Easting (m)')
plt.ylabel('Northing (m)')

# 表示
plt.show()

このコードを実行することで,次のように地図上に交通量が50000以上の主要道路のデータがプロットされます.

東京都では,湾岸部と中央部に交通量が多い道路が特に集中していることが確認できました!

3. まとめ

本記事では,GeoPandasを使用して,取得した道路交通センサス情報を整理し,地図上に可視化する方法を解説しました.GeoPandasを使用することで,地理情報データを扱うための機能を提供し,地理情報データの可視化や解析を行う際に便利です.

今回の記事を参考にして,GeoPandasを使用して,道路交通センサス情報を取得し,地図上に可視化してみてください!

参考文献

GeoPandas https://geopandas.org/en/stable/

データ分析で汎用的に使えるPython入門1:ヒストグラム, 度数, 累積度数, 累積度数割合

SENSYデータサイエンティストチーム井上です。

今回はデータ分析で汎用的に使えるPythonの記事(第1回目)になります。 EDA(Explanatory Data Analysis, 探索的データ分析)の際、よく確認する内容を関数化しておくことで作業を効率化するシリーズになります!!

今回の目的

要点

  • ヒストグラム, 累積度数, 累積度数割合を出力する汎用的な関数を作成し、作業を効率化する

今回の関数で作成されるもの

  1. ヒストグラムグラフ
  2. 度数, 累積度数, 累積度数割合表

今回の目的の関数

def plot_histogram_and_create_frequency_table(df, target_column_name, bins=None, xlabel="", ylabel="Frequency", title="histogram", color="skyblue", figsize=(10,6)):
    """
    1. Pandas DataFrameを入力に、指定した列のヒストグラムを計算し、グラフを描写する
    2. 度数、累積度数、累積割合を含むデータフレームを返す
    
    Args:
        df (pd.DataFrame): 入力のデータフレーム
        target_column_name (str): 計算対象の列名
        bins (list or np.ndarray, optional): ビンの境界を指定するリストまたはNumPy配列, Noneの場合は自動的にビンを計算
        xlabel (str): 横軸のラベル名
        ylabel (str): 縦軸のラベル名
        title (str): グラフのタイトル名
        color (str): バーの色(デフォルトは"skyblue")
        figsize (tuple): 図のサイズ
    Returns:
        result_df (pd.DataFrame): 度数、累積度数、累積割合を含むデータフレーム
    """
    # ビンの自動設定
    if bins is None:
        bins = np.histogram_bin_edges(df[target_column_name], bins='auto')
    
    # ヒストグラムの計算
    hist, edges = np.histogram(df[target_column_name], bins=bins)
    
    # 結果をデータフレームに整理
    result_df = pd.DataFrame({
        'Bin': pd.IntervalIndex.from_breaks(edges, closed='left'),  # ビンの範囲
        'Frequency': hist,  # 度数
    })
    result_df['Cumulative Frequency'] = result_df['Frequency'].cumsum()  # 累積度数
    result_df['Cumulative Percentage'] = (result_df['Cumulative Frequency'] / result_df['Frequency'].sum()) * 100  # 累積割合
    
    # ヒストグラムの描画
    plt.figure(figsize=figsize)
    plt.bar(edges[:-1], hist, width=np.diff(edges), align='edge', color=color, edgecolor='black', alpha=0.7)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.title(title)
    plt.grid(axis='y', alpha=0.75)
    plt.show()
    
    return result_df

おまけ

def dataframe_unlimited_display_in_notebook_cell(df) -> None:
    """ノートブックのこの関数を実行するセル内でのデータフレームの表示を無制限にする
    Args:
        df (pd.DataFrame): データフレーム
    """
    with pd.option_context('display.max_rows', None, 'display.max_columns', None):
        display(df)

実行例

手順1:ライブラリ&データセットの準備

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.datasets import fetch_california_housing
california_housing_data = fetch_california_housing()

手順2:データセットをデータフレームへ落とし込む

df = pd.concat([
    pd.DataFrame(california_housing_data.data, columns=california_housing_data.feature_names),  # 説明変数
    pd.Series(california_housing_data.target, name=california_housing_data.target_names[0])     # 目的変数
], axis=1)

手順3:今回の目的の関数の定義

def plot_histogram_and_create_frequency_table(...)

手順4:ヒストグラムの描写

  • 今回は目的変数MedHouseValを描写していきたいと思います!

binsを自動設定にした場合

result_df = plot_histogram_and_create_frequency_table(
    df=df,
    target_column_name="MedHouseVal",
    bins=None,
    xlabel="MedHouseVal",
    ylabel="Frequency",
    title="Histogram (California Housing dataset)",
    color="skyblue",
    figsize=(10,6)
)

binsを手動設定にした場合

result_df = plot_histogram_and_create_frequency_table(
    df=df,
    target_column_name="MedHouseVal",
    bins=np.arange(0, 5.3, 0.2),
    xlabel="MedHouseVal",
    ylabel="Frequency",
    title="Histogram (California Housing dataset)",
    color="skyblue",
    figsize=(10,6)
)

手順5:度数, 累積度数, 累積度数割合表の描写

dataframe_unlimited_display_in_notebook_cell(result_df)

binsを自動設定にした場合

出力結果(長いので折りたたみ)

Bin Frequency Cumulative Frequency Cumulative Percentage
0 [0.14999, 0.25542521739130436) 10 10 0.0484496
1 [0.25542521739130436, 0.3608604347826087) 16 26 0.125969
2 [0.3608604347826087, 0.46629565217391306) 88 114 0.552326
3 [0.46629565217391306, 0.5717308695652175) 407 521 2.52422
4 [0.5717308695652175, 0.6771660869565217) 636 1157 5.60562
5 [0.6771660869565217, 0.7826013043478262) 661 1818 8.80814
6 [0.7826013043478262, 0.8880365217391304) 777 2595 12.5727
7 [0.8880365217391304, 0.9934717391304348) 966 3561 17.2529
8 [0.9934717391304348, 1.098906956521739) 763 4324 20.9496
9 [1.098906956521739, 1.2043421739130435) 890 5214 25.2616
10 [1.2043421739130435, 1.3097773913043478) 761 5975 28.9486
11 [1.3097773913043478, 1.4152126086956522) 915 6890 33.3818
12 [1.4152126086956522, 1.5206478260869567) 886 7776 37.6744
13 [1.5206478260869567, 1.626083043478261) 1119 8895 43.0959
14 [1.626083043478261, 1.7315182608695652) 888 9783 47.3983
15 [1.7315182608695652, 1.8369534782608696) 852 10635 51.5262
16 [1.8369534782608696, 1.942388695652174) 904 11539 55.906
17 [1.942388695652174, 2.0478239130434783) 610 12149 58.8614
18 [2.0478239130434783, 2.1532591304347823) 604 12753 61.7878
19 [2.1532591304347823, 2.258694347826087) 736 13489 65.3537
20 [2.258694347826087, 2.3641295652173913) 631 14120 68.4109
21 [2.3641295652173913, 2.4695647826086953) 567 14687 71.1579
22 [2.4695647826086953, 2.5749999999999997) 471 15158 73.4399
23 [2.5749999999999997, 2.680435217391304) 478 15636 75.7558
24 [2.680435217391304, 2.7858704347826087) 480 16116 78.0814
25 [2.7858704347826087, 2.891305652173913) 367 16483 79.8595
26 [2.891305652173913, 2.996740869565217) 290 16773 81.2645
27 [2.996740869565217, 3.1021760869565216) 242 17015 82.437
28 [3.1021760869565216, 3.207611304347826) 247 17262 83.6337
29 [3.207611304347826, 3.31304652173913) 265 17527 84.9176
30 [3.31304652173913, 3.4184817391304345) 281 17808 86.2791
31 [3.4184817391304345, 3.523916956521739) 306 18114 87.7616
32 [3.523916956521739, 3.6293521739130434) 226 18340 88.8566
33 [3.6293521739130434, 3.734787391304348) 173 18513 89.6948
34 [3.734787391304348, 3.840222608695652) 160 18673 90.47
35 [3.840222608695652, 3.9456578260869564) 134 18807 91.1192
36 [3.9456578260869564, 4.051093043478261) 129 18936 91.7442
37 [4.051093043478261, 4.156528260869565) 111 19047 92.282
38 [4.156528260869565, 4.26196347826087) 109 19156 92.8101
39 [4.26196347826087, 4.367398695652174) 94 19250 93.2655
40 [4.367398695652174, 4.472833913043478) 90 19340 93.7016
41 [4.472833913043478, 4.578269130434783) 104 19444 94.2054
42 [4.578269130434783, 4.683704347826087) 53 19497 94.4622
43 [4.683704347826087, 4.789139565217391) 61 19558 94.7578
44 [4.789139565217391, 4.894574782608696) 48 19606 94.9903
45 [4.894574782608696, 5.00001) 1034 20640 100

binsを手動設定にした場合

出力結果(長いので折りたたみ)

Bin Frequency Cumulative Frequency Cumulative Percentage
0 [0.0, 0.2) 5 5 0.0242248
1 [0.2, 0.4) 35 40 0.193798
2 [0.4, 0.6) 641 681 3.29942
3 [0.6, 0.8) 1227 1908 9.24419
4 [0.8, 1.0) 1688 3596 17.4225
5 [1.0, 1.2) 1581 5177 25.0824
6 [1.2, 1.4) 1577 6754 32.7229
7 [1.4, 1.6) 1772 8526 41.3081
8 [1.6, 1.8) 1812 10338 50.0872
9 [1.8, 2.0) 1547 11885 57.5824
10 [2.0, 2.2) 1176 13061 63.28
11 [2.2, 2.4) 1256 14317 69.3653
12 [2.4, 2.6) 942 15259 73.9293
13 [2.6, 2.8) 915 16174 78.3624
14 [2.8, 3.0) 600 16774 81.2694
15 [3.0, 3.2) 466 17240 83.5271
16 [3.2, 3.4) 506 17746 85.9787
17 [3.4, 3.6) 523 18269 88.5126
18 [3.6, 3.8) 345 18614 90.1841
19 [3.8, 4.0) 255 18869 91.4196
20 [4.0, 4.2) 226 19095 92.5145
21 [4.2, 4.4) 182 19277 93.3963
22 [4.4, 4.6) 178 19455 94.2587
23 [4.6, 4.8) 106 19561 94.7723
24 [4.8, 5.0) 87 19648 95.1938
25 [5.0, 5.2) 992 20640 100

皆さんの分析の効率化の一助になれば幸いです!!

gcloudコマンドでカンマをエスケープ

SENSYプロダクトチーム藤沼です

はじめに

SENSY CLOUDでは、GCPのPub/Subを活用してシステムアーキテクチャを構成しています。本記事では、Pub/Subを用いた技術検証における便利なヒントを共有します。

やりたいこと

Pub/Subの技術検証を行う際、手元でgcloud CLIを使い、メッセージをパブリッシュして動作確認を行いたいことがあります。特に、attributesに複数の情報をカンマ区切りで渡したい場面がよくあります。

たとえば、attributesのキーtable_nameに対し、以下の3つのテーブル名を指定したい場合を考えます。

  • sales_transaction
  • store_sku_master
  • store_master

ダメな指定方法

以下のように指定すると、一見すると正しそうに見えますが問題があります。

gcloud pubsub topics publish sample-topic \
    --attribute=project=<project>,table_name=sales_transaction,store_sku_master,store_master,location=us-central1

このコマンドでは、どの部分がどのように区切られているのかが曖昧で、意図通りに動作しません。実行すると以下のようなエラーメッセージが表示されます。

ERROR: (gcloud.pubsub.topics.publish) argument --attribute: 
Bad syntax for dict arg: [list1]. Please see `gcloud topic flags-file` 
or `gcloud topic escaping` for information on providing list or dictionary flag values with special characters.

エスケープ方法

エラーメッセージに記載されている公式ドキュメントを参考に、正しい指定方法に修正します。

以下のように、--attribute=の最初に^:^を付けることで解決できます。これにより、属性間の区切り文字をデフォルトのカンマからコロン(:)に変更することができます。

gcloud pubsub topics publish sample-topic \
    --attribute=^:^project=<project>:table_name=sales_transaction,store_sku_master,store_master:location=us-central1

この指定方法を使うと、途中のカンマが区切り文字と認識されることがなくなり、table_nameキーには"sales_transaction,store_sku_master,store_master"の文字列が正しく渡されます。

最後に

SENSY / プロダクト開発チームではSENSY CLOUDを一緒に開発するデータサイエンティスト・機械学習エンジニアを随時募集しています。

herp.careers