SENSY Product Dev Tech Blog

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

データ分析のためのシェルワンライナー入門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

データ分析のためのBQ入門1(月次データ生成テクニック:SQLで簡単に歯抜けを調査を楽に)

はじめに

こんにちは、データサイエンス部の荻です。
今回は多くのデータ分析者が悩む「月次データの歯抜け問題」に役立つクエリを書いたのでご紹介します。

背景:なぜ月次データの歯抜けが問題なのか?

データ分析において、月次の集計は非常に一般的です。しかし、データが存在しない月があると、以下のような問題が発生します:

  1. グラフや表が不自然に途切れてしまう
  2. 前年同月比などの計算が正確にできない
  3. トレンド分析の精度が落ちる

こういった状況に直面したとき、「あぁ、もし月の歯抜け調査が楽にできればな……」と思ったことはありませんか?

解決策:SQLで月次シリーズを生成する

この問題を解決するため、BigQueryのSQLを使って月次シリーズを簡単に生成する方法をご紹介します。

実装方法

以下のSQLコードで、指定した期間の月次シリーズを生成できます:

-- 日付生成と書式設定のクエリ
WITH date_range AS (
  -- 2024年1月から2025年1月までの月初日を生成
  SELECT date_on
  FROM UNNEST(GENERATE_DATE_ARRAY("2024-01-01", "2025-01-01", INTERVAL 1 MONTH)) AS date_on
)

-- メインクエリ
SELECT
  date_on,
  FORMAT_DATE("%Y-%m", date_on) AS yyyy_mm_on,
  FORMAT_DATE("%Y%m", date_on) AS yyyymm_on
FROM date_range
;

このSQLを実行すると、2015年1月から2024年12月までの全ての月が、'YYYY-MM'形式と'YYYYMM'形式で生成されます。

まとめ:データ分析の効率と精度を向上させる

この月次シリーズ生成テクニックを使うことで、以下のメリットが得られます:

  1. データの歯抜けを簡単に補完できる
  2. 時系列分析の精度が向上する
  3. レポートやダッシュボードの見栄えが改善する

さらに、この方法は他のデータベースシステムにも応用可能です。ぜひ、皆さんの日々の分析作業にお役立てください。

次回の勉強会では、この技術を活用した実践的なデータ分析例をご紹介する予定です。お楽しみに!

Poetry導入

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

TL;DR


SENSYでは、依存関係管理の課題(バージョン競合や手動調整)を解消するために、Poetryを導入しました。これにより、以下の利点が得られました:

  • 依存関係管理の簡易化pyproject.tomlpoetry.lockを活用し、競合を自動解決
  • 仮想環境の統合:プロジェクトごとの仮想環境を自動生成・管理
  • パッケージの区分:開発用と本番用依存関係を分離し、本番環境に不要なパッケージのインストールを防止

Poetryの採用により、効率的で再現性のあるPythonプロジェクト管理が可能になりました。

課題


モデル開発のために突発的に使用していたこともあり、SENSYではまだrequirements.txtを使用してパッケージ管理を行なっているレポジトリが多く存在します。

ただ、最近は1つのレポジトリ内で扱うパッケージ数も多くなったこともあり、依存パッケージ間でバージョンの競合が発生した場合、自動解決が難しく手動での調整が必要になり苦労することが増えてきました。

そこで、今回はrequirements.txtの代替となるPoetryを導入してみました。

もちろんpoetryの他にもpipenvやHatchなどの代替パッケージもあるので、今後比較できればと思います。

Poetryとは


依存関係の管理

プロジェクトに必要なパッケージやそのバージョンをpyproject.tomlにまとめることができます。 今までは以下のコマンドを叩くことでrequirements.txtにパッケージとバージョンが追加されました。

pip install {パッケージ名}
pip freeze > requirements.txt
requirements.txtにパッケージとバージョンが追加されました。

依存パッケージ間でバージョンの競合が発生した場合、pip installの場合は手動で使用するパッケージのバージョンを調整することが多いのですが、poetryはこれを自動で解決してくれます。

また、Poetryではpoetry.lockを自動生成し依存関係を固定することで、異なる環境での動作の再現性を担保してくれます。

仮想環境の統合管理

pyproject.tomlはパッケージ管理をするとともに、仮装環境構築のためにも使用されます。

pyproject.tomlpoetry.lockの内容から仮想環境を自動的に作成し、依存関係をその仮想環境内で管理してくれます。プロジェクト毎に仮想環境を自動的に作成・管理できるため、依存関係の隔離が容易にできます。

パッケージのビルドと公開

弊社ではあまり行いませんが、Pythonパッケージを簡単にビルドし、PyPIPython Package Index)に公開する機能も備えています。SENSYではパッケージをビルドしDockerイメージを作成することに使う想定です。

Poetry利用による解消点


Poetryを導入することでパッケージの依存関係管理が断然楽になりました。

また、pyproject.tomlにおいて開発用/本番用のパッケージ依存関係の区分が出来るようになったので、flake8mypyなどのCIに使用する本番に関係ないパッケージが本番環境構築時にインストールせずに済むというメリットもありました。

Poetryの使い方


1. Poetryのインストール

Poetryをインストールします。(pip以外にもcurlでインストールすることも可能です。)

pip install poetry
poetry --version

2. 新しいプロジェクトの作成

新しいPythonプロジェクトを作成します。

poetry new my_project

これにより、以下のようなディレクトリ構造が作成されます。

my_project/
├── pyproject.toml
└── README.md

3. 既存のプロジェクトにPoetryを導入

既存プロジェクトでPoetryを使用する場合は以下のコマンドを叩きます。

poetry init

4. 依存関係の追加

パッケージを追加するには以下のコマンドを使用します。

poetry add requests

特定のバージョンを指定する場合は以下。

poetry add requests@^2.28

開発用依存関係の場合は以下。

poetry add pytest --group dev

一度に複数のパッケージを追加する場合は以下。

poetry add pytest mypy --group dev

5. 依存関係のインストール

プロジェクトの依存関係をインストールするには:

poetry install

仮想環境が自動的に作成され、依存関係がインストールされます。

6. 仮想環境の管理

Poetry 1.2以降では、一部のコマンド(例: shell)がプラグインとして分離されています。そのため、以下の手順でプラグインを有効化してください:

poetry self add poetry-plugin-shell
poetry shell

現在の仮想環境の情報を確認するには:

poetry env info

7. パッケージの更新

すべての依存関係を更新する場合は以下。

poetry update

特定のパッケージを更新する場合は以下。

poetry update requests

8. 仮装環境内での実行

poetry runをもとに走らせることで仮装環境内でpythonを実行することが可能です。

プロジェクトのルートディレクトリに main.py ファイルを作成し、以下の内容を書き込みます:

def greet(name):
    return f"Hello, {name}!"

if __name__ == "__main__":
    print(greet("World"))
> poetry run python main.py
Hello, World!

と出たら、仮装環境内でpythonが動くことも確認できました。

最後に


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

herp.careers