every Tech Blog

株式会社エブリーのTech Blogです。

カンファレンススタッフに参加してみよう!

f:id:nanakookada:20210906185641p:plain

はじめに

はじめまして。最近創立されたSite Reliability Engineering(SRE)チームに所属している吉田です。

今回は「カンファレンススタッフ」についてお話しようと思います。

”カンファレンススタッフをやってみたいけど、まだまで全然プログラミングできないから貢献できないかも…”という方がいらっしゃったらぜひ読んでいただければと思います!

カンファレンススタッフって何?

名の通りカンファレンスのスタッフをします。

といっても何をしてるの?という疑問が出てくると思うので、実際に僕がメインで参加しているPyConJPを例にご紹介します。

(全部はリストアップできないので一部です

  • webサイトの構築
  • タイムテーブル作成
  • プロポーザル
  • グッズ・ノベルティ作成
  • 宣伝
  • 予算組み
  • スポンサー様とのやりとり
  • 進行の台本作成
  • 企画組み
  • keynote決定
  • 通訳業者様とのやりとり
  • ロゴデザイン
  • チケット設計
  • 会場下見・決定
  • 託児所決め
  • etc…

パッと見てわかった人もいらっしゃるかもしれませんが、Pythonを使わないタスクが多いです。Pythonどころかプログラミングの知識が必要なタスクは意外とありません。

もちろんPythonの知識は生かせないというわけではなく、keynoteやプロポーザルなどではPythonの専門知識が必要となります。

お伝えしたいのは「その言語に対しての知識が無いからカンファレンススタッフは無理か」とはならないということです。これはPyConJPだけではなく他カンファレンス様でも同様のケースが多いと感じています。

f:id:yskn-beer:20210903162204p:plain
(photo by PyConJP)

モチベーション

では何故カンファレンススタッフを始めたか、続けているモチベーションは何かという話です。

IT業界に還元したい

僕の場合はプログラマーとして日々インターネットで様々な情報をインプットしています。インプットというのは誰かがアウトプットしているから成り立つものです。

それ以外にも開発でOSSを使用していますが、OSSも湧いて出てくる訳もなく誰かが時間を割いてコミットしてくれたものです。

「その行為に対して何か還元できないか」という気持ちは昔からあったのですが、当時は自分の技術的にOSSコミットは難しいな…アウトプットできるほどの知識も無いな…と思っていてなかなか踏み出せずにいました。しかしたまたまスタッフ募集のお知らせを見かけて自分にもきっとなにかできることはあるやろという精神でエイッと入ったのがカンファレンススタッフになった経緯となります。

実際に入ってみたら本当にプログラミング1mmもわからんという人でもタスクが山程あったので結果オーライでした。

面白い人に出会える

カンファレンススタッフには様々な会社・業種・レイヤー・国籍の人がいます。

個人的にはNOC(Network Operations Center)というタスクを中心に行うことがあり、そこで知り合った方たちとカンファレンス以外でも交流が続き、界隈の情報交換など色々な話が聞け非常に有意義な時間になっています。

これは小話なんですが、カンファレンススタッフの中には複数のカンファレンスを跨いでスタッフをしてる人も少なくないので、交流を持つことで他のカンファレンスのスタッフ求人を紹介してもらって数珠つなぎに色んなカンファレンススタッフに参加しやすくなることが多々あります。僕自身も複数のカンファレンスのスタッフをNOC繋がりでやったこともあり、それぞれのカンファレンス同士でスタッフが手伝いする循環が発生することがあります。

f:id:yskn-beer:20210903162424p:plain
(photo by PyConJP)

市場価値向上

これは入った後に気づいたのですが、カンファレンススタッフをやること自体が個人の実績として有用であり1エンジニアとして市場価値が付与されるというものがあります。

上記にも記載したとおりコミュニティへの貢献という文脈として社内外の評価が上がることがあります。

自分の場合はたまたまメディアスポンサー様のご協力の元カンファレンスレポートを執筆し、無事執筆実績の解除を行うことがありました。感謝します!

自分のペースで進められる/お互いリスペクトしあえる

実際にカンファレンススタッフになってみて一番思うことはカンファレンススタッフというのはあくまで有志のボランティアなので、プライベートの時間に合わせて作業が進められるという点です。

学生は学業が、社会人には仕事があるので時には振られたタスクを締め切りまでにこなすことが難しいときがありますが、そういった時でもキチンと「ちょっと期限までには厳しいです」と言うだけで手が空いている人が率先して手伝いや引き継ぎを行ってくれることが多く、非常に心理的に助かる面があります。

これは互いが本業を持っていること・プライベートの時間を割いていることを自覚していてリスペクトしあっているからこそ為せることだと思っています。

逆に本業が早めに終わったりしてプライベートの空き時間が開いてる時は残ってるタスクや、手一杯になっている他の人の手助けを積極的に行える環境が整っていることが多く無理なく自分のプライベートの時間に合わせて無理なく進めることができます。

スタッフTシャツがもらえる

寝間着が増えます

まとめ

"カンファレンススタッフ"と聞いて腰が重かった方もいらっしゃると思いますが本当にカジュアルにプライベートのスキマ時間を使ってコミュニティに貢献ができます。

「コミュニティ貢献はしたいけどそれでもカンファレンススタッフはまだちょっと…」という方も大丈夫です。コミュニティの貢献は色んな方法があり、これは一例です。財団への寄付やSNSでイベントの宣伝、その言語やフレームワークを使うこと自体で十分貢献できてきます。

イベントをスタッフとして作り上げるのは非常に達成感があります。ぜひ一緒にカンファレンス・コミュニティを盛り上げていきましょう!

f:id:yskn-beer:20210903162517p:plain
(photo by PyConJP)


P.S.

そして何故このタイミングでこういった投稿をしたかと言うと、僕が参加しているPyConJP2021が10月15・16日と開催されます!

https://2021.pycon.jp/

更に更にPyConJPではサポートスタッフを募集中です!

PyCon JP 2021 サポートスタッフ募集開始のお知らせ

Apache AirflowのPoCをした話

f:id:nanakookada:20210820142542p:plain# Apache AirflowのPoCをした話

はじめに

弊社『DELISH KITCHEN』のデータプラットフォーム上では、日々発生するデータをLakehouseプラットフォームに集約しており、Databricks上で処理される多数のETLジョブが存在しています。しかし、現在利用しているジョブ管理ツールでは、Databricksのジョブ同士の依存関係を細かく設定することが出来ず、実行ジョブが肥大化してしまう問題があります。
これらを適切な粒度で依存関係を設定出来るようにするため、DAGによるワークフロー定義が可能なApache Airflowを導入しました。その際に行ったPoCでの所感をお話します。

Lakehouseプラットフォームについてはこちらの記事で紹介されています。
Delta LakeとLakehouseプラットフォームによるデータウェアハウス設計

現状

Databricks Jobsによる管理

弊社ではデータ分析基盤として主にDatabricks を利用しており、ETLバッチジョブの多くをDatabricks Jobs を用いて管理・運用しています。
ETLバッチジョブの各タスクの依存関係や実行順を定義するワークフローは、DatabricksのNotebook上で定義されます。これらのジョブのスケージュール、アラートなどの設定はDatabricks JobsのWeb UI上から行っています。

これらのジョブは次のサンプルコードのように、Notebook上で定義して処理されます。

// Bigqueryからイベントの生ログを転送し、イベント毎に保存するジョブ
// task01,02が順次実行される
dbutils.notebook.run("./bronze/01_ExtractEventDataFromBQ")
dbutils.notebook.run("./silver/02_TransferAppEventToDelta")

次の図のように、Web UI上からスケージュールなどの設定をします。

f:id:ngmk:20210818153922p:plain
Databricks Jobsスケジュール設定画面

実際のアプリケーションのジョブでは、以下のようなデータ処理が実行されています。

  • BigQuery上のApp/Webのイベントの生ログを転送後、イベント毎に分解して保存
  • S3上に保存されるサーバーログをイベント毎に保存
  • 各事業部のKPIを計算

Databricks Jobsによるジョブ管理の問題点

これらのDatabricks Notebookで処理されるジョブには、ジョブ間の依存関係を設定できない問題が存在します。この問題は、処理方式がCRONとなっており、時間制御によるジョブスケジュール管理ツールのため、他のジョブの状態を考慮していないことから発生しています。
ジョブの完了をトリガーとしたジョブの実行ができず、トリガーとなるジョブのワークフロー内に後続ジョブの実行を定義する必要があります。
ジョブのワークフロー内に別のジョブ実行を定義することは、本来ジョブが持つ関心事を曖昧にし、データワークフローの見通しが悪くなるだけでなく、データワークフロー管理の観点やジョブ肥大化の観点などにおける様々な問題を引き起こします。

例えば以下のような3つのジョブがあるとします。

  • アプリのログを保存する
  • アプリ内検索のKPIを計算する
  • アプリ内課金のKPIを計算する

検索、課金のKPIを計算するジョブは、アプリログを保存するジョブの完了を実行条件として要求します。
3つのジョブの流れを図に起こすと次のようになります。

f:id:ngmk:20210818182916p:plain
3つのジョブのワークフロー

各ジョブには論理的な繋がりがなく、関心事毎に個別のジョブとして定義・管理されている状態が望ましいですが、Databricks Jobsでは依存関係の設定ができないため、本来は個別に定義されるべきジョブが1つのジョブとして定義されてしまいます。

例えば、アプリログを保存するジョブの完了をトリガーに、各KPIを計算するジョブの実行が不可能なため、1つのジョブ内でそれぞれのジョブを定義することになります。
次の図はそれぞれのジョブが同一のノートブック内に1つのジョブとして定義されている状態を表します。

f:id:ngmk:20210818183010p:plain
依存関係を設定できない場合のワークフロー

単一のジョブとして定義された各ジョブは、順次実行されるため実行時間が長くなるだけでなく、ジョブ毎に要求される適切な計算資源の割当ができません。そのため最も重い処理に要求される計算資源を長時間使い続けることになります。大きな計算資源を長時間使用することは、ジョブの運用に要求されるコストを増加させるため望ましくありません。

例えば、KPIの計算はアプリログを保存するジョブと比較して計算資源を要求しませんが、ジョブをまとめて定義してしまったことで、必要な計算資源よりも大きな計算資源が割り当てられてしまい、不必要なコストの増加が起こります。

このように、関心事によって適切な粒度でジョブを分割し定義されていない状態は以下のような問題を引き起こします。

  • ジョブの肥大化による実行時間の長期化
  • ジョブの計算量に応じた適切な計算資源の分配ができない
  • 上記2つに起因するコストの増加
  • 将来的なワークフロー変更に対応できない
  • データワークフローの見通しの悪化

Apache Airflowの導入

ジョブ間の依存関係を設定できない問題を解決するため、Apache Airflow を導入します。
Apache Airflowはジョブ間の依存関係を設定できるだけではなく、GCP、AWS、Databricksなどの弊社で利用している様々なサービスへのタスク実行をサポートしているため、検討することにしました。

Apache AirflowではETLバッチジョブのワークフローをDAG(有向非巡回グラフ) として、1つのPythonファイルで定義します。これらのジョブのスケジュール、アラートなどの設定もDAGを定義したPython ファイル内で定義され、ワークフローだけではなくジョブの設定を含めたコードベースでの管理が可能です。

次のサンプルコードのように、Databricks上のジョブを実行するDAGを定義できます。

# need pip install airflow==2.0.2, apache-airflow[databricks]==2.0.2

import datetime

from airflow import DAG
from airflow.providers.databricks.operators.databricks import

DatabricksRunNowOperator, DatabricksSubmitRunOperator
from airflow.utils.dates import days_ago

cluster_settings = {}

with DAG(
        # アラート、スケジュールを設定
        dag_id='example_databricks_jobs',
        default_args={
            'owner': 'admin',
            'depends_on_past': False,
            'email_on_failure': False,
            'email_on_retry': False,
            'retries': 2,
            'retry_delay': datetime.timedelta(seconds=10)
        },
        schedule_interval='@hourly',
        start_date=days_ago(2)
) as dag:
    task1 = DatabricksRunNowOperator(
        task_id='run_now_operator',
        job_id='65',  # databricks > jobs > job ID
        notebook_params={'env': 'prd'}
    )
    task2 = DatabricksSubmitRunOperator(
        task_id='submit_run_operator',
        json={
            'notebook_task': {
                'notebook_path': 'Product/bronze/01_ExtractEventDataFromBQ',
                'base_parameters': {'env': 'prd'}
            },
            'new_cluster': cluster_settings
        }
    )
    # ワークフローを定義
    task1 >> task2

定義したDAGによるワークフローは、ダッシュボード内のGraph Viewによって可視化できます。データワークフローが可視化されることは、依存関係の素早い把握に繋がり、多数のジョブの運用を助けます。

次の画像は上記のDAGによるワークフローが可視化されたもの表しています。

f:id:ngmk:20210818154549p:plain
airflowによるワークフローの可視化

ジョブ間の依存関係の考慮

Apache Airflowでは、ExternalTaskSensorモジュールという他のDAGに定義されているタスクの実行結果(成功/失敗)を検知するモジュールを使用することで ジョブ間(DAG間)の依存関係を設定できます。
このモジュールを後続として実行したいDAGの最初のタスクとして定義すると、アプリログを保存するジョブの終了後にKPIを計算するジョブを実行するなどのジョブの依存関係を設定したワークフローを表現できます。ジョブの実行結果を検知するタスクを後続のワークフロー内に定義することで、トリガーとなるジョブ内に後続ジョブの実行を定義する必要がなくなります。
ジョブのワークフロー内に別のジョブ実行を定義する必要がなくなったことは、ジョブが持つ関心事を明確にし、データワークフロー管理の観点やジョブ運用の観点などに様々な利点をもたらします。

次の図のようにジョブを個別のDAGとして定義できます。

f:id:ngmk:20210818183055p:plain
依存関係を設定したワークフロー

ジョブを個別に定義することで、並行実行による実行時間の短縮や、ジョブごとの適切な計算資源の割当により、不必要な計算資源を長時間使い続けることがなくなります。 必要な計算資源を必要な時間だけ使用することにより、ジョブの運用に要求されるコストが最適化されます。

例えば、アプリのログを保存するジョブには大きな計算資源を、各KPIを計算するジョブには小さな計算資源を割り当て、KPI計算を並行して実行することで、ジョブ全体での実行時間の短縮と計算資源の適切な割当により運用コストを抑えることができます。

このように関心事が分離され、適切な粒度でジョブを分割することで以下のような恩恵を受けます。

  • ジョブの計算量に応じた適切な計算資源配分
  • 上記によるコスト削減
  • ジョブの並列実行によるワークフロー全体で見た実行時間の短縮
  • ワークフロー変更に対する柔軟性
  • データワークフローの見通しの好転

最後に

以上、ETLバッチジョブの管理ツールとしてApache AirflowをPoCした話でした。
Apache Airflowを導入したことで、ジョブ間の依存関係を考慮したワークフロー定義が可能になり、適切なジョブの定義によりで様々な恩恵を得ることができました。
ジョブを関心事毎に適切な粒度で定義することで、各ジョブの見通しがよくなる、コストが最適化されるなどの恩恵を享受でき、多数のETLバッチジョブを長期に渡る管理・運用を可能とすることが期待されます。

参考

  1. Databricks
  2. Jobs | Databricks on AWS
  3. Apache Airflow
  4. Apache Airflow Concepts
  5. 有向非巡回グラフ - Wikipedia

エブリーでインターンをしています

f:id:nanakookada:20210812114528p:plain

はじめに

はじめまして。 2021年2月から、インターンとしてデータ&AIチームでデータエンジニア業務に携わっている金安です。 入社からの約半年間、データに関わる多種多様なタスクを経験させていただきました。 ここではエブリーでのデータ分析の様子を紹介するとともに、業務を通して学んだことを整理しようと思います。

いきさつ

私は大学で情報処理技術・人工知能技術について勉強しており、アカデミックな研究の道と、ものづくりに携わるエンジニアとで進路に迷っていました。 そのような中で、何となく参加した逆求人イベントでエブリーのCTOとお話しする機会があり、インターンでエンジニアとして就業することになりました。 プログラミング歴も浅く、実務経験も全くない私を採用していただき感謝しています。

現在はエブリーで週2日業務に従事しながら、大学で量子熱力学・量子情報科学分野を研究しています。 熱力学に登場するエントロピーと情報理論におけるエントロピーは本質的に等価であることからもわかるように、両者の間には接点があります。 これに注目し、情報理論の知見を物理学に持ち込むことで、非平衡熱力学など未完成の理論の発展に貢献する、というのが当分野の一つの目標です。 私の研究は、近年発見された「熱力学不確定性関係」という不等式にスポットが当てられています。 これはエネルギーと精度の関係を示唆すると解釈することができ、実用的には、量子コンピューターの性能限界の理解に繋がります。 総じて分野横断的な研究であり、情報が物理世界で果たす役割を新たな視点から探ることができます。

エブリーのデータ分析基盤

エブリーでは、毎日大量のデータを集計し、加工・分析して得た知見を施策に反映させるデータ分析のOODAループが回っています。 OODAは、Observe, Orient, Decide, Act の頭文字で、意思決定プロセスにおける4つのステップにあたります。

第一ステップは観察(Observe)で、データを集計・分析し、事実を集めることに相当します。 これをもとに、仮説構築(Orient)を行います。 これは今後の施策の方向性を決める非常に重要なプロセスです。 仮説を立てたら、具体的にどのような施策を行うかを決定(Decide)します。 最後に施策を実行(Act)します。 以上のステップを何度も繰り返すことで、変化し続ける状況の中で、適切な判断を下し、迅速に行動に移すことが可能になります。 これがOODAループです。

このデータによるOODAループを回す根底には、DIKWモデルが前提としてあります。 DIKWは Data, Information, Knowledge, Wisdom の頭文字で、下図のようなピラミッド構造になっています。

DIKWモデルのイメージ
DIKWモデル

(出典:The Knowledge Pyramid: A Critique of the DIKW Hierarchy)

集めてきただけの生のデータ(Data)は、乱雑で整理されていないうえに重複や欠落が含まれている可能性もあり、それだけでは意味を持ちません。 これをクレンジングし、何らかの基準で整理して初めて、情報(Information)として意味を見出すことができるようになります。 さらにここから、特定の観点で情報を凝集させたり、統計的な数量を算出したりすることで、より抽象度の高い知識(Knowledge)や知恵(Wisdom)に昇華させることができます。 これらは施策を行う上での判断の根拠となり、OODAループを回し、データから価値を生み出すことに繋がります。

エブリーでは、これに則ったデータ分析基盤が構築されています。 大量の生データが、Google BigQuery や Amazon S3 といったストレージに集約されます。 これを、databricksというデータ分析プラットフォームを通して加工・分析・凝集し、目的ごとに多様なテーブルを作成しています。 さらにこれをRedashというツールで集計・可視化し、組織の意思決定に役立てています。

最近体験した業務とその振り返り

ここでは、私がインターン生として最近取り組んだタスクについて振り返ってみます。

エブリーが様々な施策やサービスを運用する中で、「施策の定量的な検証・可視化をより高速に行いたい」という課題がありました。 これを達成するため、Redash上で高度な統計処理を行い、よりカジュアルに統計情報を処理できる可視化基盤を作成する、という目標が立てられました。 RedashではもともとPythonスクリプトを実行できますが、通常はRedashのコンソール上で直接コードを入力して実装します。 しかしこの方式には、実装コードがGitHubで管理されない、テストが行われていないなどの問題がありました。 そこで、統計処理用のスクリプトをモジュール化したものを、Redashサーバにデプロイすることを検討しました。

このタスクは、直接データに触れるよりは、サービスを運用するインフラ構成の把握が主だったのですが、周辺知識がほぼ0だった私にとっては、次から次へと学びを得られて非常に新鮮でした。 Redashのサーバが構築されているdockerコンテナに触れてみたり、そのサーバからS3上に通信してファイルを受け渡してみたり、Pythonライブラリが0から作られる様子を見てみたりと、新しい体験の連続でした。

特に印象的だったのが、チームリーダーからの「GitHubでmergeすると、どうしてそのコードが本番環境で動き始めると思う?(意訳)」という言葉です。 私はそこで初めて、GitHub上からサーバにコードをデプロイしている、circleCIというツールの存在を知りました。 確かに言われてみれば、git上でコードのバージョン管理をしても、全然違う場所にあるサービスのコードが勝手に書き換わるわけはないのですが、そんなことは考えたことも疑問に思ったこともありませんでした。

このRedashサーバへのモジュールデプロイは、客観的には特別難易度の高いタスクではないと思いますが、本当に有意義な体験ができたと感じています。

おわりに

エブリーでは日々実力不足を痛感させられていますが、同時に成長を実感することもあり、業務は非常に充実しています。 今後ともよろしくお願いします!


エブリーでは、エンジニア以外にもインターン生を募集しています。 興味を持たれた方は、こちらをご覧ください。

A/Bテストにおける評価指標選定の話

f:id:nanakookada:20210805165140p:plain

はじめに

 はじめまして。2021年4月にエブリーに入社した山西と申します。
 データサイエンティストとしてデータ関連部門に所属後、DELISH KITCHENアプリ改善施策のA/Bテストに約3ヶ月間従事してまいりました。
 今回はその実業務の中での体験も踏まえ、A/Bテストにおける評価指標の選定プロセスや苦労したポイントなどを紹介していきます。

A/Bテストについて

 DELISH KITCHENのアプリでは機能の改善に向けたUIやアプリ内動線等の変更を定期的に実施しております。
 このようなアプリ機能の変更はユーザーの体験を多かれ少なかれ変化させるものであるため、ユーザー視点、および事業視点で良い効果が見込めるものを見極めたうえでユーザー全体へ展開するのが望ましいといえます。
 そこでエブリーでは、プロダクトマネージャーがアプリ機能変更施策を企画し、データサイエンティストがA/Bテストによりそれらの効果を事前検証することで、変更を施すべきか否かの意思決定への還元を行っています。

 A/Bテストは、文字通りAパターン(変更前)とBパターン(変更後)の結果を比較する手法です。
 「とあるアプリ機能の変更施策」の文脈においては、一部ユーザー(テスト群)のみに介入した結果の効果を、変更を施さなかったユーザー(コントロール群)と比べて検証する手法とも言い換えられます。
 あらかじめ検証期間を定めて行うのが一般的であり、その期間内でテスト群がコントロール群よりもどの程度良くなったか(または悪くなったか)を評価指標を用いて統計的な見地から判断する、という流れをとります。
 以下、その流れを簡潔にですが整理します。

A/Bテストの流れ

 プロダクトマネージャー、データサイエンティストが連携しつつ実施することとなります。
 データサイエンティストが主体となるのは2~6のフェーズとなります。

f:id:yama24every:20210804164939p:plain
A/Bテストの流れ

1. 施策の企画
 プロダクトマネージャーが施策の案を企画し、データサイエンティストへ共有、内容の精緻化を行う。

2. 評価指標の選定
 施策の効果を測るために事前設定しておく指標を選定する。
 ※ とある機能の継続率や訪問回数などのユーザーのエンゲージメントを測る指標や、CTRやCVRなどのビジネス上注視すべき指標が例として挙げられる。

3. 対象ユーザーの抽出
 アプリ利用ユーザーをランダムに抽出した後、コントロール群(施策を実施しないグループ)とテスト群(施策を実施するグループ)に分ける。

4. 施策の実施
 テスト群のユーザーに対してアプリ機能の変更を適用する。

5. 結果の集計
 アプリのログを各評価指標へと集計するSQLクエリを作成。
 コントロール群とテスト群それぞれに対して算出した評価指標値の有意差検定や信頼区間の推定を行う。
 各集計値の比較結果を表や時系列グラフで表現し、ダッシュボードにまとめる。

6. 結果の報告
 プロダクトマネージャーにダッシュボードを共有すると共に結果を報告する。
 有意差の有無や効果量の結果レポート、及びそこから得られる考察を述べつつ議論する。

7. 意思決定
 A/Bテストの結果を省みて、施策を全ユーザーに適用するか否かの判断、または追加施策の実施の有無について判断を下す。

 本記事では以後、2.の評価指標を選定するプロセスについて掘り下げていきます。

評価指標の選定

 前述の通り、施策の効果による「良し悪し」をA/Bテストによって見極めるためには、その目的にあった評価指標を設定する必要があります。
 エブリーでは現在、以下の指標分類フレームワークをその選定基準として採用しております。

指標の分類

 これらの分類は書籍『A/Bテスト実践ガイド』にて提唱されているものです。
 要点を整理し紹介いたします。

  • ゴール指標

    • ビジネスにおける最終的な目標を直接反映した指標。
    • 施策に携わるステークホルダー間で広く受け入れられるようなシンプルなものであるのが望ましい。
  • ドライバー指標

    • ユーザー体験等を元にした、ビジネス目標を間接的に表現する指標。
    • 短期的に観察することが出来、その変動が捉えやすいという特徴を持つ。
    • ゴール指標の代理指標としての役割も担う。
  • ガードレール指標

    • 施策が問題なく進行しているか確認するための指標
    • 予期せぬバグ等でユーザー体験が悪化してないかを確認する指標

 また、優れたオンライン実験を設計するためには各指標が『短期的(実験期間内で)に測定可能であり、計算可能であり、十分に敏感(分析感度)で即時的(即時性)に反応するものでなければならない。』ことが書籍『A/Bテスト実践ガイド』で指摘されています。
 つまり、計算するためのログが存在し集計可能であること、それらが有限である検証期間内に測定可能であり、さらにその変化が観察できることを確かめたうえで、ゴール、ドライバー、ガードレールの枠組みに照らし合わせながら評価指標を選んでいくことになります。
 このようなA/Bテストに向けた評価指標に求められる観点は、ビジネスでの報告目的で用いられる指標の観点とは一致しないケースが多々あることにも留意する必要があります。

指標の実装

 施策の目的と指標分類のフレームワークを照らし合わせながら評価指標を洗い出し、それらが集計可能であること(元となるログが存在していること)を確認した後は、各々の評価指標を集計するためのSQLクエリを作成します。
 その後、ダッシュボード上にこれらのクエリを登録し、表やグラフとして可視化します。
 このようにして、A/Bテストの結果をダッシュボードを軸に報告、考察および意思決定へ還元する準備が整います。

f:id:yama24every:20210804165130p:plain
ダッシュボード可視化の例

大変だったこと

 次に、施策の目指す向きをうまく表現する指標を選定したり、それを実装へ落とし込むために元となるログ周りを理解したり、さらにプロダクトマネージャーと連携したりする過程で大変だったことについて紹介します。

指標の選定作業

ゴール指標の代理指標の設定

 本来は上記の指標の分類にて挙げたゴール指標で施策の良し悪しの意思決定が出来れば理想ですが、実際には代理指標(指標の分類でいうところのドライバー指標)を設定し、これを主要な指標として観察していく場合も多々あります。
 主な理由として以下2点が挙げられます。

  • ゴール指標が遅行指標(数値として現れるには一定の期間が必要な指標)であり、施策実施期間終了まで、すなわちビジネスにおける意思決定のタイムリミット時点までに十分な観察が行えない。

  • ゴール指標には関連する因子が多くあることが想定されることから、施策によってその一部に介入を行っただけではほとんど変化しない恐れがある。

 この際には代理指標としての妥当性(擬似相関になっていないか、因果関係まで整理できているか)の模索に、多くのリソースを割かなければなりません。

施策の影響範囲を見極めた集計

 ひとえに施策の効果を測るといっても、集計対象とするユーザーをどう選ぶかでその意味合いは大きく変わります。
 例えば「アプリトップ画面→機能A→機能B→機能C」と遷移するアプリ内動線の中で機能Cに介入するA/Bテストを実施する場合、機能Cを利用したユーザーのみ集計するのか、それより前の動線のユーザーも集計対象に含めるか、を注意深く設定しないと求めたいものと乖離する場面が多々ありました。
 求めたいものに応じてどう集計するか、以下2パターンに整理します。

「使った人がどう反応するか」を知りたいとき

 機能の使い心地に介入するような施策を打った場合は、その機能が利用されない限り効果を測ることが出来ないため、機能利用実績があるユーザーのみを集計対象とするのがふさわしいといえます。
 ユースケースとしては「ユーザーが必ず1回は利用または訪問するであろう機能のデザインに対して介入する施策を打ったとき、2回目以降の利用が伸びるか否か」を判断したいとき等が挙げられます。

「使わなかった人がどれだけ使ってくれるようになったか」も含めて知りたいとき

 アプリ内動線の追加や、デザイン変更等で機能の利用を促す施策を打った場合はその機能の獲得傾向に興味があるため、注目する機能を利用していないユーザーも集計対象となります。
 このケースではまず施策検証期間内に、アプリトップ画面にアクセスしたA/Bテスト対象ユーザー全体を対象に集計することが多くなります。
 しかし、「注目する機能があまり使われていないときに指標を平均値として集計したい」場合は、単なる全アプリアクセスユーザーの平均値を出すと0に非常に近い値が出てしまい意味のある解釈が難しくなってしまいます。
 その際には集計対象を「該当機能により近いアプリ内動線を利用したユーザー」に絞ったうえで平均値を算出する場面もありました。

 このようにして「どのユーザー集団に対して平均を取り、そこから何を知りたいのか」をイメージしながら意味のある指標に落とし込むにはアプリのUI/UX側の深い理解が必要だと感じました。
 アプリ内の機能、動線にどのようなものがあるか把握しておくことはもちろん、各機能のアプリ利用傾向を普段からモニタリングしておくことで、求める分析軸に対して適切な指標を設計する目を養っていきたいと思いました。

ログ周りの理解

ログの仕様および状態の確認

 評価指標を実装するためには当然その元となるログの存在が前提となるため、選定時点でそれらの仕様を頭に入れておく必要があります。
 また、アプリ内OSの特定のバージョンでのバグ発生等により、ログが汚染され評価指標として正しく集計できなくなっている場合は、事前にその影響を排除する措置を取ったうえで指標をデザインする必要があります。
 しかし、複雑なクライアント・サーバーサイドの実装背景を咀嚼したり、大量に存在するアプリ内動線から必要なログを洗い出したり、さらにそれらが正しく記録されているかをモニタリングしたりするのは中々に骨が折れる作業でもあり、入社後しばらくは評価指標一つの集計クエリを書くだけでも多くの時間を費やす日々が続いてしまいました。
 こうした経験から、アプリ内のユーザーの動きがログとしてどのように反映されているか、そのログが何処に格納されているか、さらに仕様変更や更新、バグ等にも目を張り巡らせておくこともデータサイエンティストの重要な役割であることを実感いたしました。

実装したい評価指標に対応するログが存在しない場合の対処

 もし評価指標の実装に必要なログが存在しない場合は、以下のいずれかの選択肢を取ることになります。

  • クライアント・サーバーサイドで新たに実装してもらう

  • 代替、近似できるような既存ログを探す

 これらのうちどの方向に舵を切るかは、当該指標のビジネス的な重要性、意思決定までのタイムリミットから逆算した施策の実施期間、他部署のリソース等の間のトレードオフ関係の熟慮ののちに各ステークホルダーと相談することによって決定されることとなります。
 データ側の視点から、こうした調整を円滑に進めることの難しさも実感しました。

プロダクトマネージャーとの連携

わかりやすく説明する能力

 流れの説明にて解説した通り、A/Bテストの狙いはその分析結果をプロダクトマネージャーへ報告し、そこから意思決定につなげる洞察を得ることです。
 それを円滑に行うため、「データサイエンスの見地をわかりやすく、かつ誤解無く説明する」能力がデータサイエンティスト側に求められることとなります。
 評価指標を解釈する観点、有意性の解釈の仕方、懸念事項などの多くの情報をどのようにしてまとめれば簡潔にアウトプットできるか、試行錯誤の毎日です。

評価指標についてのすり合わせ

 指標の分類の項でも述べたように、プロダクトマネージャーが追跡するKGIなどのビジネス指標とA/Bテストとして設定する評価指標は必ずしも一致しない場合が多くあります。
 例えば、一般的にKGIとして利用されるビジネス指標の多くは短期的に変化を観察するのが困難であり、そのままA/Bテストで追っていくとすると多くの場合ゴール指標の代理指標の設定にて説明した懸念に直面するため、この場合はより効果検証にふさわしい代理指標を主に見ていくことになります。
 そのため「なぜA/Bテストとしてその評価指標を選定したのか」「どう解釈すれば良いか」等の認識をプロダクトマネージャーとデータサイエンティスト側でしっかりと認識を合わせておく必要があります。

最後に

 「コントロール群とテスト群を比較する」というコンセプトだけ聞くと単純明快な印象を受けがちなA/Bテストですが、それを良質な意思決定へつなげるためには一筋縄にはいかない工夫が必要であることをこの数ヶ月間の業務で改めて認識しました。
 分析要件とアプリ側のドメイン知識を相互に照らし合わせつつ、試行錯誤も繰り返しながら最適な指標を見定める力を付けていきたいと思います。
 最後までお読みいただきありがとうございました。

参考文献

Ron Kohavi,Diane Tang,Ya Xu,大杉 直也.(2021)「A/Bテスト実践ガイド 真のデータドリブンへ至る信用できる実験とは (Japanese Edition) 」

Alex Deng, Xiaolin Shi. Data-Driven Metric Development for Online Controlled Experiments: Seven Lessons Learned. In Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, KDD, 2016

Apache SparkのSparkSQLのstack関数を用いてデータを横持ちから縦持ちにする

f:id:nanakookada:20210729160653p:plain

はじめに

はじめまして。 データストラテジストの田中です。普段は『DELISH KITCHEN』レシピ視聴実態の可視化やオーディエンス配信のレポート作成、サービス好意度の分析などの業務を行っています。

サービス好意度など定性的な要素が多い分析ではWEBアンケート調査のデータを活用していますが、WEBアンケート調査のローデータは質問内容がカラムとして横持ちで存在することが多いのが特徴です。
今回はデータベースでも扱いやすいよう「Apache Spark環境下で横持ちのデータを縦持ちにする」TIPSをお伝えします。

stack関数を用いて縦持ちにする

Spark SQLにてデータを横持ちから縦持ちにするにはstack関数を使用します。
stack関数については、Apacheより公式ドキュメントが提供されていますので、詳細は下記リンクをご覧ください。

Apache公式ドキュメント

サンプルデータ

横持ちのサンプルデータとして、アンケートデータに近い形のものを用意しました。

+------+------+---+------+--+--+-----------+--+
|sample|gender|age|  area|Q1|Q2|         Q3|Q4|
+------+------+---+------+--+--+-----------+--+
|   AAA|     1| 40| Tokyo| 1| 5|    特になし| 0|
|   BBB|     2| 15| Shiga| 2| 6| アプリを見て| 1|
|   CCC|     1| 20| Osaka| 3| 7|   広告を見て| 0|
|   DDD|     1| 55|Nagoya| 4| 8|       null| 1|
+------+------+---+------+--+--+-----------+--+

各ユーザID(sample)毎に付帯情報(gender 〜 area)と質問内容(Q1 〜 Q4)の回答結果が1行ずつ積まれています。今回はユーザIDと付帯情報に紐づく形で、質問内容を縦持ちにしたい場合の実装例を提示いたします。

実装方法

ドキュメント通りですとstack関数はSELECT stack(n, col1, col2 ...)と記述し、「col1, col2 ...」を「n」行で分割するといった仕様になります。
サンプルデータではSELECT stack(4, Q1, Q2, Q3, Q4)と記述しても良いのですが、実際のケースシナリオを想定した場合、汎用的に使えるよう質問内容のカラム情報を動的に取得できることが望ましいです。汎用性を加味した実装例を以下に提示します。

実装例

import org.apache.spark.sql.functions._

val result = data.columns.filter(x => x.contains("Q")).map{
  v =>
    data.select($"sample", $"gender", $"age", $"area",expr(s"stack(1,'$v',$v) as (q_id, q_value)"))
}.reduce(_ unionByName _)

display(result)

質問内容がスケールすることを考慮し、columnsを使用しカラム名を取得、filterを使用し質問内容のカラム名のみ抽出を行っています。 抽出したカラム名の値をmapにて、1つずつ読み出し、縦積みにしています。$vのようにカラム名を指定すると回答結果だけが縦積されるため、カラム名も'$v'で取得する形にしています。
最後に質問内容のカラム情報を1つずつ縦積みしたものをreduce(_ unionByName _)で結合しています。

出力結果

各ユーザIDと付帯情報に紐づく、質問内容のカラム名q_idと回答結果q_valueの一覧を出力することができました。

+------+------+---+------+----+------------+
|sample|gender|age|  area|q_id|     q_value|
+------+------+---+------+----+------------+
|   AAA|     1| 40| Tokyo|  Q1|           1|
|   BBB|     2| 15| Shiga|  Q1|           2|
|   CCC|     1| 20| Osaka|  Q1|           3|
|   DDD|     1| 55|Nagoya|  Q1|           4|
|   AAA|     1| 40| Tokyo|  Q2|           5|
|   BBB|     2| 15| Shiga|  Q2|           6|
|   CCC|     1| 20| Osaka|  Q2|           7|
|   DDD|     1| 55|Nagoya|  Q2|           8|
|   AAA|     1| 40| Tokyo|  Q3|      特になし|
|   BBB|     2| 15| Shiga|  Q3|   アプリを見て|
|   CCC|     1| 20| Osaka|  Q3|    広告を見て|
|   DDD|     1| 55|Nagoya|  Q3|        null|
|   AAA|     1| 40| Tokyo|  Q4|           0|
|   BBB|     2| 15| Shiga|  Q4|           1|
|   CCC|     1| 20| Osaka|  Q4|           0|
|   DDD|     1| 55|Nagoya|  Q4|           1|
+------+------+---+------+----+------------+

また別のサンプルデータを元に、Databricksのdisplay関数でプロットを作成しました。
縦持ちのテーブルのメリットは下記プロット結果のように質問全体での足し上げがしやすいことと、その他2軸以上のグラフを作成するときにも便利なことです。

サンプルデータ

f:id:blacktiger126:20210729144746p:plain
サンプルデータ

プロット結果

f:id:blacktiger126:20210729140324p:plain
サンプルプロット

最後に

実装自体はシンプルですが、stack関数の実装例が少ないと感じたため取り上げてみました。
最後まで閲覧いただきありがとうございました。