Apache SparkのGraphFramesを用いた定時フライトのパフォーマンス

[On\-Time Flight Performance with GraphFrames for Apache Spark \| Databricks Blog](https://www.databricks.com/blog/2016/03/16/on-time-flight-performance-with-graphframes-for-apache-spark.html)の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

:::note info
2016年の記事です。
:::

# 導入

数多くの問題においてグラフ構造はより直感的なアプローチとなります。ソーシャルネットワークの経路、レストランのレコメンデーションやフライト経路などにおいて、グラフ構造の文脈でこれらの問題を理解する方が容易です: すなわち、頂点、エッジ、プロパティです。たとえば、フライトデータの分析は、空港は*頂点*、フライトは*エッジ*によって表現されるので古典的なグラフ問題と言えます。そして、これらのフライトには、出発の遅延、飛行機のタイプ、キャリアなど数多くの*プロパティ*が関連づけられます。

この記事では、グラフ構造でまとめられたフライトパフォーマンスデータをクイックかつ容易に分析できるように、Databricksノートブックで([Introducing GraphFrames](https://www.databricks.com/blog/2016/03/03/introducing-graphframes.html)で発表された)GraphFramesを活用します。グラフ構造を活用しているので、構造的なモチーフの特定、PageRankを用いたエアポートのランキング、都市間の最短パスのようにテーブル構造では直感的ではない数多くの質問を容易に行えるようになります。GraphFramesはクエリーをシンプルにし、Apache SparkのSQLエンジンのパフォーマンス最適化を活用するために、DataFrame APIの分散処理、表現能力を活用します。さらに、GraphFramesを用いる事で、Python、Scala、Javaでグラフ分析を行うことができます。

# GraphFrames Sparkパッケージのインストール

GraphFramesを使うには、はじめに[GraphFrames Spark Packages](https://spark-packages.org/package/graphframes/graphframes)をインストールする必要があります。Databricksでのパッケージのインストールは数ステップのシンプルなものです。

spark-shell、pyspark、spark-submitでのGraphFramesの参照方法は以下のようになります。

```sh
$SPARK_HOME/bin/spark-shell --packages graphframes:graphframes:0.1.0-spark1.6
```

# Flightデータセットの準備

我々のグラフを構成する二つのデータセットは、[OpenFlights Airport, airline and route data](https://openflights.org/)で取得できる`airports`データセット(頂点)と[Airline On\-Time Performance and Causes of Flight Delays: On\_Time Data](https://www.bts.gov/explore-topics-and-geography/topics/airline-time-performance-and-causes-flight-delays)で取得できる`departuredelays`データセット(エッジ)です。

[GraphFrames Spark Package](https://spark-packages.org/package/graphframes/graphframes)をインストールしたら、以下のようにすることで頂点、エッジ、(PySparkの)GraphFrameをインポート、作成します。

```py
# Import graphframes (from Spark-Packages)
from graphframes import *

# Create Vertices (airports) and Edges (flights)
tripVertices = airports.withColumnRenamed("IATA", "id").distinct()
tripEdges = departureDelays.select("tripid", "delay", "src", "dst", "city_dst", "state_dst")

# This GraphFrame builds upon the vertices and edges based on our trips (flights)
tripGraph = GraphFrame(tripVertices, tripEdges)
```

例えば、tripEdgesには`origin`のIATA空港コード(src)、`destination`のIATA空港コード(dst)、都市(city_dst)、州(state_dst)、出発遅延(delay)を特定できるフライトデータが含まれます。
![](https://www.databricks.com/wp-content/uploads/2016/03/tripEdges-1024x382.png)

# tripGraphグラフフレームに対するシンプルなクエリー

tripGraphグラフフレームができたので、ご自身のグラフフレームをクイックにナビゲートし理解するために、数多くのシンプルなクエリーを実行することができます。例えば、グラフフレームに含まれる**空港と移動の数を理解する**ために、以下のPySparkコードを実行します。

```py
print "Airports: %d" % tripGraph.vertices.count()
print "Trips: %d" % tripGraph.edges.count()
```

これは以下のアウトプットとなります:

```
Airports: 279
Trips: 1361141
```

GraphFramesはSparkにおけるDataFrameベースのグラフであるため、DataFrame APIを活用した高度に表現的なクエリーを記述することができます。例えば、以下のクエリーによって、SFO空港からの出発が遅延した(delay > 0)フライト(エッジ)をフィルタリングし、平均遅延時間を計算しソートすることができます。これはすなわち、**SFOから出発したどのフライトが、重大な遅延を引き起こす可能性が高いのか？** という質問をしていることになります。

```py
tripGraph.edges\
.filter("src = 'SFO' and delay > 0")\
.groupBy("src", "dst")\
.avg("delay")\
.sort(desc("avg(delay)"))
```

アウトプットを確認することで、このデータセットではSFOからWill Rogers World Airport (OKC)、Jackson Hole (JAC)、Colorado Springs (COS)へのフライトにおいて平均的に大きな遅延が生じていることをクイックに特定することができます。
![](https://www.databricks.com/wp-content/uploads/2016/03/SFO-significant-delays-1024x550.png)

Databricksノートブックでは、地図上にクイックに可視化することもできます: **SEAから出発する際、どの州へのフライトで大きな遅延が起きる傾向があるのか？**
![](https://www.databricks.com/wp-content/uploads/2016/03/SEA-delays-by-state-map-1024x617.png)

# フライト遅延を理解するためにMotif Findingを活用

都市の空港とフライト間の複雑な関係性をより簡単に理解するために、フライトで接続された空港のパターンを特定するためにモチーフを活用することができます。結果は、モチーフのキーが与えられたカラム名を持つデータフレームとなります。

例えば、**SFO起因の遅延は何か？** という質問を尋ねるためには、以下のようにシンプルなモチーフを生成することができます。

```py
motifs = tripGraphPrime.find("(a)-[ab]->(b); (b)-[bc]->(c)")\
.filter("(b.id = 'SFO') and (ab.delay > 500 or bc.delay > 500) and bc.tripid > ab.tripid and bc.tripid > ab.tripid + 10000")
display(motifs)
```

SFOをcity(b)に接続することで、任意の目的地(c)へのフライト[bc]の前に、SFO(b)に接続する任意の出発地(a)からのすべてのフライト[ab]を検索します。それぞれのフライト([ab] or [bc])の遅延が500分以上で2番目のフライト(bc)が最初のフライト(ab)から1日以内に発生しているものにフィルタリングしています。

このクエリーによって得られる、カラム名がそれぞれのモチーフのキーとなっている簡易版のサブセットを以下に示します。
![Screenshot 2024-01-26 at 9.26.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/6a25d3ff-0e72-41c1-ebc3-a296d236771e.png)

このモチーフ特定クエリーによって、このデータセットにおいてHoustonやTusconからSan Franciscoには定刻あるいは少し早くに出発していることをクイックに特定できます[1011126]。しかし、SFOでの接続フライトを利用している乗客[1021507]は536分の遅延を体験しています。

# 最も重要な空港を特定するためにPageRankを活用

GraphFramesはGraphXをベースとしているので、すぐに利用できる数多くのビルトインのアルゴリズムがあります。PageRankはGoogle Search Engineで人気を博し、ラリー・ペイジによって開発されました。Wikipediaによると:

> PageRank works by counting the number and quality of links to a page to determine a rough estimate of how important the website is. The underlying assumption is that more important websites are likely to receive more links from other websites.

上の例はwebページを指しているものですが、このコンセプトの素晴らしいところは、Webページ、バイクステーション、空港であろうが任意のグラフ構造に適用でき、インタフェースはメソッド呼び出しのようにシンプルなものになっているということです。また、GraphFramesではアルゴリズムを実行した後に分析を継続できるように、シンプルな方法で頂点のデータフレームの新規カラムとして、PageRankの結果が返却されることに気づくことでしょう！

このデータセットにはさまざまな空港を経由する膨大な数のフライトと接続が含まれているので、それぞれの空港がどれだけ重要なのかをラフに見積もるために、PageRankアルゴリズムを活用することができます。

```py
# Determining Airport ranking of importance using pageRank
ranks = tripGraph.pageRank(resetProbability=0.15, maxIter=5)

display(ranks.vertices.orderBy(ranks.vertices.pagerank.desc()).limit(20))
```

以下のグラフに示すように、PageRankアルゴリズムを用いる事で様々な頂点(空港)間の接続(フライト)の質によると、Atlantaが最も重要な空港の一つと考えることができます。これは、[Atlanta is the busiest airport in the world by passenger traffic](https://en.wikipedia.org/wiki/List_of_the_world%27s_busiest_airports_by_passenger_traffic#2015_statistics)という事実と一致しています。
![](https://www.databricks.com/wp-content/uploads/2016/03/airport-ranking-pagerank-id-1024x335.png)

# フライト接続の決定

様々な都市間で数多くのフライトがあるため、二つの都市間の経路を特定するためにGraphFrames.bfs (Breadth First Search)手法を活用することができます。以下のクエリーでは、最大パス長を1(ダイレクトフライト)にして、San Francisco (SFO)とBuffalo (BUF)間の経路を特定しようとしています。結果セットは空となっています(つまり、SFOとBUF間にはダイレクトフライトはありません)。

```py
filteredPaths = tripGraph.bfs(
fromExpr = "id = 'SFO'",
toExpr = "id = 'BUF'",
maxPathLength = 1)
display(filteredPaths)
```

それでは、SFOとBUF間で1つの接続を持つ`maxPathLength = 2`を指定してクエリーを拡張しましょう。

```py
filteredPaths = tripGraph.bfs(
fromExpr = "id = 'SFO'",
toExpr = "id = 'BUF'",
maxPathLength = 2)
display(filteredPaths)
```

SFOとBUF間の経路のサブセットは以下のようになります。
![Screenshot 2024-01-26 at 9.40.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/6b28f11c-3a4d-b9f5-d77f-5c0d35993a09.png)

# D3を用いたフライトの可視化

このデータセットのフライト経路と接続に関するパワフルなビジュアライゼーションを得るために、Databricksノートブックで[Airports D3 visualization](https://mbostock.github.io/d3/talk/20111116/airports.html)を活用することができます。GraphFrames、DataFrames、D3のビジュアライゼーションを組み合わせることで、このデータセットにおいて定時あるいは早期に出発したすべてのフライトにおけるすべてのフライト接続のスコープを可視化することができます。青い円は頂点、すなわち空港を表現し、円のサイズはエッジ、この空港で到着、発着するフライトの数を示しています。黒い線はエッジ、フライトを意味しており、他の頂点、空港への接続を表現しています。スクリーン外に伸びているエッジは、HawaiiとAlaskaの空港を示していることに注意してください。

<iframe width="560" height="315" src="https://www.youtube.com/embed/riJGOmKF3Bs?si=QYFu880FYf50P3Ma" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)