読者です 読者をやめる 読者になる 読者になる

検索ログから「じわじわ検索頻度が上昇しているキーワード」を見つける

こんにちは。トレンド調査ラボの井上寛之(@inohiro)です。

普段は法人向けサービス「たべみる」の開発を担当しています。 たべみるはクックパッドの検索ログを基にしたサービスで、任意のキーワードの検索頻度、キーワード同士の組み合わせ検索頻度、 およびそれらを地域や年代・性別で絞り込んで分析することができます。

トレンド調査ラボでは「たべみる」の開発のほか、 クックパッド上のトレンドを見つけるために日々調査を行っています。 ここでのトレンドとは、「流行っている」もしくは「流行りそう」といったものを指します。 消費者が気になっているキーワードが何かを知ることで、消費者が求めている情報を適切に提供できると考えています。

今回は、膨大な検索ログの中から「じわじわ検索頻度が上昇しているキーワード」を見つけるために 行ったことについて紹介したいと思います。

じわじわ検出

「じわじわ検索頻度が上昇している流行りそうなキーワードを、機械的に、いち早く見つける」ことを 「じわじわ検出」と呼ぶことにします(「流行り」については後述します)。

例えば最近流行したキーワードとしては「塩レモン」や「おからパウダー」などがあります。 下図は、「おからパウダー」「ガパオライス」「塩レモン」の、2009年3月から2015年2月までの週間検索頻度の推移です。

f:id:InoHiro:20150629154133p:plain

このようなキーワードを、青い矢印で指すような流行りはじめのうちに見つけられると嬉しいわけです。

そもそも「流行っている」とは?

そもそも「流行っている」とはどういうことなのでしょうか。 じわじわ検出で検出対象となるようなキーワードの検索頻度推移から、共通する点を見つけ出し、 式のように定義できると検出ができそうです。つまり、「流行っている」の定義がこの分析の肝と言えます。 試行錯誤を繰り返し、現在は「流行っている、流行りつつある」を以下のように定義しています。

  • 「1年前の検索頻度を上回っている週数 / 比較対象の週数(N)>= M」であるとき、流行っている、流行りつつある
    • N:比較対象の週数(52週、40週、27週など)
    • M:検索頻度が1年前の同一週よりも上回っている週のNに対する割合(70%、80%、90%など)

文章として書いてみると、「N週の間で、週の検索頻度が一年前の同一週の検索頻度と比べて上回っている週の割合が、M%を超えている」とき、 そのキーワードが流行っている、流行りつつあるということです。

例えば以下の図は、N=10、M=90%で「2015年流行っている」と判定されるような例です。 10週(N)のうち、2015年の90%の週が2014年を上回っており、M >= 90% の条件を満たしています。

f:id:InoHiro:20150629154155p:plain

一方以下の図は、「流行っている」と判断されない例です。 2014年を上回る週は60%であるため、M >= 90% の条件を満たしていません。

f:id:InoHiro:20150629154214p:plain

今回はNに57から27のような大きめの値を設定しましたが、このように設定した理由には次に挙げるような背景があります。

  • 季節要因を排除したい

    • 例えばクリスマスやバレンタインデー、お正月などの季節要因による影響を抑えたい
  • バースト(一過的な爆発的な数値の変化)を排除したい

    • 主にテレビで取り上げられたことによるバーストを排除したい

Nが小さいと、調査対象の期間によっては季節的な要因によって「流行っている」ように見えてしまう可能性があります。 同様に、短い期間のうちにバーストした場合、非常に大きな影響を受ける可能性があります。 そのため、Nは大きめの期間(52~27)を使うのが良いと考えました。

下図は「ガパオライス」の検索頻度のうち、2014年第2-26週と2015年2-26週を比べたグラフです。 ほぼ全ての週で、2015年の検索頻度が2014年の検索頻度を上回っているのが分かります。 絶対値はどうであれ、検索されている(≒多くの消費者が注目している)キーワードであると言えます。 また、ある程度の期間で比較しないと、季節や、テレビなどによるバーストの影響を受けてしまう可能性がある ことが分かると思います。

f:id:InoHiro:20150629154234p:plain

一方、季節要因やバーストの影響を受けない代償として、新語(これまで存在しなかったワード)を見落とす可能性があると言えます。 新語の場合、前52週のうちの多くが検索頻度0である可能性が高く、急に検索され始めたワードは今回の定義では見落とす可能性が高いです。 ただし新語の発見は「じわじわ検出」の目的では無いので無視します。

SQLによる分析

SQLを使って、時系列データから流行っている、流行りつつあるキーワードを探してみました。 SQLを使う理由は、分析対象となるキーワードごとの週間検索頻度が、 Amazon Redshiftに蓄積されているためです。 分析の流れは以下のようにしました。

  1. 年ごとに週番号を付ける
  2. 1年前の同一週とくらべて検索頻度が上回っている週を見つけ出し、その割合を計算する
  3. 対象週数Nと、上回っている収集の割合Mを指定して、最終的なキーワード集合を得る

前提

前提として、分析対象のデータは以下の様なスキーマのテーブルに入っているとします。

create table weekly_si
( data_date date   -- 週の開始日
  , keyword_id int -- キーワードID
  , si float       -- 検索頻度
)
;

1. 年ごとに週番号を付ける

row_number()(ウインドウ関数)で、年ごとの週番号をつけたリレーションを作っておきます。 なおwith句を使えば、いちいち表を作らずに2と一緒に実行することができます。

create table numbered_weekly_si
( week_num int     -- 週番号
  , data_date date -- 週の開始日
  , keyword_id int -- キーワードID
  , si float       -- 検索頻度
)
;

insert into numbered_weekly_si
select
    row_number() over(partition by keyword_id, extract(year from data_date) order by data_date) as week_num
    , data_date
    , keyword_id
    , si
from
    weekly_si
;

2. 1年前の同一週とくらべて、上回っている週にフラグを立てつつ、N週のうちの割り合いを計算する

1で作った週番号付きの検索頻度を、年をずらしてジョインし、1年前の同一週と比較を行います。 1年前を上回っている場合フラグ(si_growth)を 1 に、そうでなければ 0 としておきます。 この結果に対して、ウインドウ関数を使って比較対象の週数Nにおける、上回っている週数の割合を求めます。 以下のクエリでは、52週だけでなく、40週、27週についても集計しています。

create table weekly_si_growth
(
  week_num int             -- 週番号
  , current_data_date date -- 週の開始日
  , si_growth int          -- 検索頻度が成長しているか (0,1)
  , keyword_id int         -- キーワードID
  , ratio52 float          -- 前52週での検索頻度成長割合
  , ratio40 float          -- 前40週での検索頻度成長割合
  , ratio27 float          -- 前27週での検索頻度成長割合
)
;

insert into weekly_si_growth
select
    *
from (
    select
        week_num
        , current_data_date
        , si_growth
        , keyword_id
        -- 週数N {52, 40, 27} 毎に、1年前同一週の検索頻度を上回っている週の割合を計算
        -- 分母は定数を直接書くこともできるが、必ずしも {52, 40, 27} 週前まであるとは限らないので、
        -- 同様に数え上げる
        , sum(si_growth) over(partition by keyword_id order by current_data_date rows 52 preceding) * 1.0
                / sum(1) over(partition by keyword_id order by current_data_date rows 52 preceding)
              as ratio52
        , sum(si_growth) over(partition by keyword_id order by current_data_date rows 40 preceding) * 1.0
                / sum(1) over(partition by keyword_id order by current_data_date rows 40 preceding)
                as ratio40
        , sum(si_growth) over(partition by 1 order by current_data_date rows 27 preceding) * 1.0
                / sum(1) over(partition by 1 order by current_data_date rows 27 preceding)
                as ratio27
    from (
        select
            current.week_num
            , current.data_date as current_data_date
            -- 1年前の検索頻度を上回っていたら 1 とする
            , case when (current.si - last_year.si) > 0 then 1 else 0 end as si_growth
            , current.keyword_id
        from
            numbered_weekly_si current

            -- 1年前の同一週とジョイン
            inner join numbered_weekly_si last_year
            on current.week_num = last_year.week_num
                and current.keyword_id = last_year.keyword_id
                and extract(year from current.data_date) = extract(year from last_year.data_date) + 1
    )
    group by
        1, 2, 3, 4
)
;

3. パラメータ(対象週数N、週数の割合M)を指定して、条件を満たすワードを得る(検出)

2で、全てのキーワードについて、週数N(52週、40週、27週)での検索頻度が上昇している割合を計算したので、 期間と検出に用いる週数N、閾値となる割合Mを指定して、その条件を満たすワードを出力します。 ここでは下記のパラメータで問合せるクエリを示します。

  • 期間:2014-01-01 から 2014-12-31
  • N: 52週
  • M: 90%
    • つまり、2014-01-01から2014-12-31のそれぞれの週から52週前までの期間において、1年前の同一週の検索頻度を上回っている週の割合が 90%以上であるようなキーワード

appeared_sumには期間内(上の条件の場合52週)のうち、 前の52週(N)の中で1年前の同一週の検索頻度を上回っている週が90%である週の数が入ります。 つまり、appeared_sumは期間中の出現頻度で、多ければ多いほど、 流行っている、流行りつつあるキーワードと言えます。

select
    candidate.*
    , keyword.name
    , appeared_sum
from (
    select
        keyword_id
        , sum(appeared) as appeared_sum
    from (
        select
            keyword_id
            , count(*) as appeared
        from
            weekly_si_growth
        where
            ratio52 >= 0.9 -- 前52週において、上回っている週が 90% 以上
            and current_data_date between date '2014-01-01' and date '2014-12-31'
        group by
            keyword_id
    )
    group by
        keyword_id
) candidate

-- キーワード名を得るためのジョイン
inner join keywords keyword on candidate.keyword_id = keyword.id
;

比較する週数(N)を調整することで、季節やテレビなどの影響を平均化したり、その逆を行うことができます。 今回は52週のときのみ取り扱いますが、40週、27週の値を使うと、より敏感にトレンドを掴むことができます(言うまでもなく、その分ノイズも増えます)。

検証

見つけ出したかったキーワードを、上記の方法で本当に見つけられるのか検証を行いました。

検証方法

2014年および2015年上旬に流行った、流行りつつあったキーワードのうち、 少なくともこれだけは見つけ出したかったというキーワードを正解ワードセットとし、それらを見つけ出せるか調べました。 正解ワードセット(25ワード)は以下になります。

豚ロース薄切り, おからパウダー, サンドイッチ, ジャージャー麺, ジャーマンポテト,
トースト, ミネストローネ, ラタトゥイユ, ポップオーバー, マッシュポテト,
スクランブルエッグ, 水菜サラダ, バタービール, フラックスオイル, 白湯(パイタン)鍋,
マシュマロ, 安納芋, ガパオライス, なす煮びたし, 温泉卵の作り方,
塩レモン, マッサマンカレー, 亜麻仁油, 台湾まぜそば, 寝かせ玄米

また、前年同一週を上回っている週数の割合(M)は、小さくすればそれだけ検出する語が増えるので、 90% → 80% → 70%と変化させてみました。

検証結果

前年同一週を上回っている週数の割合が90%のとき

  • 検出した語: 3133 ワード
  • 正解数: 15 ワード(以下)
豚ロース薄切り, おからパウダー, ジャーマンポテト, ミネストローネ, ラタトゥイユ,
マッシュポテト, スクランブルエッグ, 水菜サラダ, マシュマロ, 安納芋,
ガパオライス, なす煮びたし, 温泉卵の作り方, 塩レモン

前年同一週を上回っている週数の割合が80%のとき

  • 検出した語: 5988 ワード
  • 正解数: 21 ワード(90%の時に見つけたワードと、以下)
サンドイッチ, ポップオーバー, バタービール, マッサマンカレー, 台湾まぜそば, 寝かせ玄米

前年同一週を上回っている週数の割合が70%のとき

  • 検出した語: 10089 キーワード
  • 正解数: 22 ワード(80%の時に見つけたワードと、以下)
ジャージャー麺

結果について

適合率(Precision)を上げすぎて今後流行る可能性のあるキーワードを落としてしまうよりも、 検出される語が多くなってしまっても、正解ワードが多く含まれる結果(高い再現率(Recall))を目指しました。 結果として再現率は、M=90%で0.6、M=70%で0.88となりました。

正解データのうち、発見できなかったキーワード

正解キーワードのうち、発見できなかったキーワードはなぜ発見できなかったのでしょうか。 それぞれのキーワードについて調べてみると、以下の様な理由であることがわかりました。

  • トースト、フラックスオイル

    • 検索頻度が1年前同一週とくらべて増加していない
  • 白湯(パイタン)鍋

    • 検索頻度が上昇し始めたのは2014年11月ごろから
    • 52週のうち90%で検索頻度が上昇しているとは言えない

そもそも「トースト」「フラックスオイル」の2ワードについては、 組み合わせて検索される頻度が上昇したことによって 「見つけ出したかったキーワード」(正解ワード)とされていたため、 今回の方法では見つけ出すことができませんでした。

2015年流行りそうなキーワードは?

この方法を2015年上旬の検索ログに適用し、流行語の候補を探してみました。 期間を2015年1月1日から6月20日までとして、N=52週、M=90%として実行したところ、1490ワードを得ました。 その中から、特にこれは流行るかも!?というような候補をいくつか挙げておきます。 ウェブやテレビ、お店などで見つけたら、このエントリのことを思い出して下さい。

その他試したアイデア

実際に今回試したアイデアに至るまでは、多くの試行錯誤を繰り返しました。 試したアイデアをいくつかご紹介しようと思います。

  • あるキーワードの検索頻度が、初めて閾値Nを超えたなら、そのキーワードは流行りはじめているのではないか?
  • 前の週の検索頻度とくらべた差が一定期間、M%以上の上昇を続けていたら、そのキーワードは流行っているのではないか?
  • 検索頻度の移動平均がL%ずつ上昇を続けていたら、そのキーワードは流行っているのではないか?
  • 組み合わせて検索されているキーワードの数が爆発的に増えているキーワードは、流行っているのではないか?
    • 実際には「組み合わせた検索が増える」というのは、探しているキーワードの認知度が高くある必要があるので、見つけることができても既に流行ってしまったキーワードであった(目的である流行りはじめは検知できない)
  • 検索頻度の変化率X%以上の立ち上がり検知し、その後も数週間にわたって検索頻度を維持し続けているとき、流行った状態にあるのでは?
    • テレビ等によるバーストを、流行ったと誤検知したくない
    • こちらも流行ったことはわかるが、流行る前に見つけ出したい

まとめ

本稿では、検索ログから「じわじわ検索頻度が上昇しているキーワード」を機械的にいち早く見つけ出すために 検討し、実際に行った方法、および得られた結果についてまとめました。

今後も定期的に「じわじわ検索頻度が上昇しているキーワード」を見つけるため、方法の改善を行っていこうと考えています。 また、人間の手による温かみのあるパラメータ調整ではなく、機械学習などを用いてより低コストに導き出せないか、 挑戦していこうと考えています。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/