コネヒト開発者ブログ

コネヒト開発者ブログ

「JQL」は、mixpanelの抱える1つの限界を打ち砕く夢である

こんにちは、PHPなどを書いている金城 (@o0h_) です。ここ暫くはファミリーマートのリーフレタスと大根のサラダセブン-イレブンの15品目のサラダボウル(和風ドレ)が私の食生活においてヘビロテしています。*1

f:id:o0h:20150214152429j:plain

話は変わるのですが、コネヒトでは、ログの収集+データの分析用途のツールの1つとしてmixpanelを利用しています。

mixpanelが凄いな、と感じる点

データの送信はシンプルなAPIリクエストで行われ、データの分析も割と踏み込んだフィルタリングやビジュアライズが手軽にできます。非常に柔軟なデータ構造・・「People」と「Event」という主語+動詞的な関係性を意識した設計は、多様なデータを格納しやすくデータ同士を組み合わせて利用するというニーズに潔く答えていると思います。

それと同時に、限界もある

その一方で、この「何でも収まる、柔軟な設計」というポテンシャルを100%引き出そうと考えると・・やはりツールとしての限界を感じることもあります。
これはすなわち、「画面上でできること」が「ユーザーの持つデータへの欲望」に対して足かせになる場面があるということです。ブラウザ上で動作するSaasの解析ツールですから、ある程度「誰にでも使えるUI、コンセプトでなくてはならない」という制約があります。
日進月歩で増長するユーザーのデータへのニーズや高まる分析リテラシーを全てカバーしようとすれば、天才しか理解できないような複雑怪奇でとっつきにくいUIになってしまうでしょう。
ですが、データ分析の世界に潜れば潜るほど、自ずとニッチな要求というのは生まれてくるものです。

例えば、

  • 「1ヵ月前にサインアップして、今日までに5回以上商品を購入しているユーザーにおいて、キャンペーンページA・B・Cそれぞれの接触状況(接触したクリエイティブはどれか、接触頻度はどうか)別に購入総額にどのような差異はあるか?」
  • 「投稿から5分以内・10分以内・30分以内にレスがついたポストにおいて、獲得できるlike数の期待値はどのようになっているか?」

といったデータはどうでしょう。
興味はそそられるものの「複雑度の高い分析項目」のような感じがあり、mixpanelでのレポーティングとして扱おうとするにはちょっと骨が折れそうです。もしくは、当初からそのような分析用途をイメージしながらトラッキング項目を設計しておければ話は別ですが・・やはり分析とは「データに潜れば潜るほど」新しい観点が生まれてくるものだと思うので。

「壁」に対するmixpanel流の回答

普段から自社DBやアクセスログなどを触っているエンジニア、データサイエンティストな方々ですと、この問題に対して「mixpanelに送信しているログデータを、こちら(利用者)で自在に絞ったり組み合わせたりできればいいのに!」というもどかしさを覚えると思います。
例えば私自身としては「このSurveyの結果をこのユーザー情報と紐付けて一覧出力したいのに!!」なんて事が実際にありました。これについては、さほど難しい要求でもないような気がしますが・・・どうやら「ユーザー情報(People)」と「アンケート内容(Survey)」を紐付けつつ一覧で!というのはmixpanelの設計思想から少し外れているのか*2、両者を結合しての表示は出来なかったのです。 *3

当然、mixpanelとしても同様の課題意識を抱いていたようです。
彼らのブログ上ではこのように綴られています。

it became increasingly apparent that our customers had a subset of questions that were very complex and could not be answered using our built-in reports.
(quoted from: Introducing JQL: A Query Language for Analytics - The Signal)

そして2016年の春、見事にこのジレンマを打破するであろう1つの機能が公開されました。
JQLです。

mixpanel.com

JQL・・・?

JavaScript Query Languageの略称で、ざっくばらんにいえば「mixpanel上のデータ(People, Event)を直接ソースとして扱い、JavaScriptベースの言語で、map/reduce/filter操作を柔軟に行う」ものです。
例えば、公式のドキュメントを見ると以下の様なことが可能だと書いてあります。

  • 3週間前にアカウントを作成して、1人以上の友人を紹介し、今週4個以上のメッセージを送った人のID(distinct id)を取得する
    • 「アカウントを作成した」「友人を紹介した」「メッセージを送った」というイベントが記録されていれば実現できるように
  • 「サインアップ」のファネルを通過したユーザーが、次にどこをみているのか?を見つけ出す
    • 「サインアップのファネル」が設定されていることは勿論ですが、更に(事前設定のない)経路の導出が可能
  • 先月解約したユーザーに、解約する前にどんな事が起きていたのかを特定する
    • 「先月解約したユーザー」に絞って、定性的なログデータを吐き出して観察する

他にもコードサンプルのページには

  • 購入プラン別に、サインアップ〜購入までに要した平均日数を調べる
    • 「購入(購入プランが分かるように)」のイベントと「サインアップ」のイベントがあれば実現可能

なんてものも紹介されていました。
どうでしょうか、「トラッキングする設計は非常にシンプルに保ちつつ、事後処理を頑張って複雑な分析を実現する」イメージが湧いては来ませんか?

今回は、このJQLについて簡単な利用ケースを交えつつ少し見ていきたいと思います。

JQLに触れてみる

まさに百聞は一見に如かず、という内容だと思うので早速コードを交えた説明に入りたいと思います。

エディタを開く

mixpanelのツール画面内から「JQL Editor」というアプリケーションが利用できるので、そちらを使います*4
ログイン後に展開されているサイドメニューの Applications > JQLをクリックし、 + NEWします。
f:id:o0h:20160808203140p:plain
ここで元々用意されているテンプレートを選択することも出来ますし、自身で全く新規に記述することもできます。
今回は、Build Your Ownでいきましょう!

スクリプティング

弊社では、ユーザーに対してmixpanelを利用したアンケート(Survey機能)を実施することが時折あります。
調査結果を簡単に見るだけなら問題ないのですが、複雑な集計ステップを踏んだり自由なビジュアライズを行いたいという要求は常に生じます。正直mixpanel単体では荷が重い部分でもあり、表計算ソフトやBIツールを用いて集計したいのです。
そのため、それぞれの票をCSVで吐き出したい!というニーズがありました。

FAQには、まさにこのニーズに即した回答が寄せられています。
今回は、このコードを題材としてJQLの世界観を覗いてみましょう!
※ 記事中のコードを一部修正して掲載しています

function main() {
  var surveyId = 'YOUR SURVEY ID';
  return People()
  .filter(function(user){
    var isSurvey = false;
    _.each(user.properties.$answers, function(v,k) {
        if(v.$survey_id == surveyId) {
          isSurvey = true;
        }
    });
    return isSurvey;
  })
  .map(function(user) {
    var answers = {};
    _.each(user.properties.$answers, function(v,k) {
        if(v.$survey_id == surveyId){
          answers["Collection ID"] = v.$collection_id;
          answers["Question ID - " + v.$question_id] = v.$value;
        }
    });
    return {
      "distinct_id": user.distinct_id,
      "City": user.properties.$city,
      "Suvery": answers
      }
  });
}

(まず、この行数で今まで辿りつけなかったデータにアクセスできるという事実に興奮しませんか・・・)

これをエディターに貼り付けてRUN QUERY ボタンを押すと、ユーザーを行にとり「ユーザーのID(distinct id)」「ユーザーの居住地(city)」及び「Surveyの各回答内容」を列に取るテーブルデータとして出力されます。
その出力イメージが、下の画像です。(内容はダミー)
f:id:o0h:20160808214131p:plain
例えば、「堺市の」「ID:974番さんは」「セグメント:6091に属し」「アンケートに対して、それぞれ A TV 7と回答している」という見方です。
また、結果の表の右上に Export ボタンがある通り、これを簡単にCSVでエクスポートする事が可能です!

出力イメージを確認したところで、コードの流れをおおまかに追ってみましょう。
※ちなみに、Underscore.jsが利用可能とのことです。*5

main()

この名称で定義された内容が処理されることになります。
必ず設置してください。

People()

「People」をリソースとしてとること意味します。
このあとにチェーンしていく処理で、欲しいデータを抽出・加工していくことになります。
Peopleの他に、 EventsやCombining events and peopleといった内容が利用可能です。
詳細はJQL - API Reference - Mixpanel | Mobile Analyticsをご覧ください。

.filter()

リソースに対してフィルタを適用します。今回で言うと、Peopleを絞り込んでいきます。
具体的な内容としては、

  • (Peopleの単数系である)userに、surveyの回答内容である「answers」というデータがあるので、
  • そのうちから、指定した「survey id」に属するanswerのみを抽出

している事が見てとれるかと思います。
なお、survey idに関してはmixpnel上のSurveysのレポート画面のURLから調べることが可能です。
https://mixpanel.com/report/[account id]/surveys/#dashboard/[survey id] という構造になっているので、抜き出してください。

.map()

絞り込まれた結果(リレーショナルDBでいうと に当たります)に対して、出力したい内容(同じく、 に当たるイメージです)をセットしていきます。
Collection IDというのはSurveyの設定画面で言う「Group」に当たり、セグメントされたユーザーの集団です。

最終的に出力する(行ごとの)内容を、 returnで渡しています。
userのプロパティからdistinct_id及びcityを、それからこのメソッド内で整形したanswersが最終的な出力結果として定義されています

以上が、今回扱ったJQLでの処理の概要となります。 どうでしょう、とてもシンプルで応用イメージの湧きやすい造りだと思いませんか?

応用

今回は「SurveyのID」という静的に近い情報で処理をしましたが、もちろん動的な条件でのフィルタリングであったり(map処理で)ひと加工を加えた集計も可能です。
例えば一例として挙げられている内容にユーザーの利用が多いemailのドメインTOP10といったものがあります。
これは単純に「emailを集計する」だけでは適わず、map処理を挟んだことで初めて「emailの中からドメインだけを取得する」ことで可能となった例です。

また、今回は紹介をしていませんがgroupByUserというのも強力な処理ですし、reduceも存在します。
使い方に関しては、是非Get StartedやExampleを覗いてみてください!

最後に

ある意味で「汎用的な入出力機構を用意したから、勝手にデータ取って行ってね!」という丸投げ・・に近いのがこのJQLだとも言えるのですが、だからこそ利用者の独創性に無際限に応え続けることになります。filter -> map/reduceというエンジニアにとっては馴染み深く直感的であるやり方を用意した事も、ハードルをグッと下げるのに一役買っているのではないでしょうか。
また、「事後的に複雑な処理を行うことが可能になった」という事実は、「計測するデータをシンプルに・最低粒度に保つ」判断をユーザーに与えます。これはmixpanelの持つ従来の思想・ポテンシャルをより豊かに引き出す・・!ものになるのではないかと、個人的には打ち震えているところです。

それと同時に、1度書いたクエリは保存できるというのも非常に心強い点として挙げておきます。定点観測用途で用いたり他のユーザーと共有したり(例えば、エンジニアが書いてディレクターが実行するといったシナリオ)が手軽なので、チーム内での活用の幅がより広がるはずです。*6
先にも触れましたが、実行結果はワンクリックでCSVエクスポートも可能です。 こういった点も「データ分析のコストとハードルを下げる」という観点で、とても嬉しく感じました。
単なるレポーティングツールから分析プラットフォームへの転化、とでも言うべきでしょうか。

弊社もまだまだ使いこなせているとはいえないレベルですが、データは活用すればしただけチームを加速させる武器になるものですので、更なる理解をこの先も深めていきたいです。
ぜひ、このエキサイティングな新機能に触れてみてくださいね!


おまけ: 参考文献、関連リンク

まだあまり日本語の情報が多くなく、英語でも正直ググりにくい・・と思っています。
ただ、めちゃくちゃパワフルな機能であり利用していないと損かもな、というのが私個人の感想です。

これからやってみよう!という方の一助となれば思い、公式のドキュメントを中心に、参考にしたリンクをまとめてみます。
この記事中に貼り付けたページも改めて紹介していますので、気になるものがあったら覗いてみてください。

  • Introducing JQL: A Query Language for Analytics - The Signal
    なんでJQLを作ったの、といった内容が書かれたブログです。「データを直感的に扱うって話ならSQLだよね〜・・と最初は思っていた!*7」といった話もあり面白かったです。
  • JQL - Mixpanel | Mobile Analytics
    JQLの紹介ページです。ちゃんと作られていて、ちょっとワクワクします 笑
  • JQL - Overview - Mixpanel | Mobile Analytics
    Referenceです。Over View / Getting started guide / API Reference / Examplesとセクションがあり、それぞれのボリュームもそんなに大きくはないので最初から順番に目を通してみるのが良いかもしれません。
  • Latest JQL & ReportKit topics - Mixpanel Community
    mixpanelのコミュニティで、JQLカテゴリです。
  • Query console
    ブラウザ内のコンソール上で、サンプルデータに対してクエリを走らせてみることができます。Exampleに書いてある内容で遊んでみたい、といった場合はこちらで。

最後までお付き合いいただきありがとうございました!

*1:[ちょっと宣伝]コンビニサラダについて一家言ある方を、職種とわず募集しています。 ウォンテッドリーからどうぞ!

*2:ユーザーの振り分けにはセグメント設定ができるので、本来的には「セグメントごとの動向を見る / そのためにGroupを設定しておく」というのが流儀なのかも知れません。もちろん、回答それぞれにおいて回答主の情報を表示する・・・というのは可能なのですが。

*3:最終的に、この時にはData Export APIを利用して対応しました。処理自体は複雑ではないものの割と面倒で・・・

*4:APIからJQLによるデータ集計を実行させることもできます

*5:At Mixpanel we use Underscore.js to improve our code readability and consistency. While it's not necessary to know Underscore to use JQL or Mixpanel Platform, knowing a few basic Underscore functions will help in understanding this tutorial (and in our opinion, make your JavaScript cleaner). https://mixpanel.com/help/reference/jql/tutorials/funnel-dropoffs

*6:JQL requires you to write code. The primary users are developers and data scientists, though it is possible to save queries and build reports for others to use.https://mixpanel.com/help/reference/jql#target-audience

*7:Yahoo: YQLやFacebook:FQLといったQuery Languageも、確かにSQLを意識していますよね