Help us understand the problem. What is going on with this article?

Python+GraphvizでF1コンストラクターの変遷を可視化してみた

More than 1 year has passed since last update.

はじめに

昔は夢中になって見ていたF1でしたが、見なくなってから十数年…
久しぶりにF1に関する記事を読んでみたら、あの頃強かったウィリアムズがテールエンダーと化し、当時はいなかったメルセデスのワークスが強いらしい…
だいたいメルセデスのワークスってどこを買収して参入してきたんだ?ってことが気になって、コンストラクターの変遷を可視化してみようと思い立ちました。

いきなり結果

PythonとGraphvizでこんな感じに可視化できました。
screenshot1.png
(中略)
screenshot2.png

えっ、メルセデスってティレルの流れなの!?

やってみたこと

元データ

元データは手作業で作成。Pythonにてimportして使うことを想定。年ごとのコンストラクターズランキングとコンストラクター名がわかるような形式にしました。

constructors_data.py
constructor_ranking = [
    (1980, [
        (1, 'ウィリアムズ'), (2, 'リジェ'), (3, 'ブラバム'), (4, 'ルノー(1977)'),
        (5, 'ロータス(1958)'), (6, 'ティレル'), (7, 'アロウズ(1978)'),
        (8, 'フィッティパルディ'), (9, 'マクラーレン'), (10, 'フェラーリ'),
        (11, 'アルファロメオ(1979)'), (0, 'ATS'), (0, 'エンサイン'), (0, 'オゼッラ'),
        (0, 'シャドウ')
    ]),
    (1981, [
        (1, 'ウィリアムズ'), (2, 'ブラバム'), (3, 'ルノー(1977)'), (4, 'リジェ'),
        (5, 'フェラーリ'), (6, 'マクラーレン'), (7, 'ロータス(1958)'),
        (8, 'アロウズ(1978)'), (9, 'アルファロメオ(1979)'), (10, 'ティレル'),
        (11, 'エンサイン'), (12, 'セオドール'), (13, 'ATS'), (14, 'マーチ(1981)'),
        (0, 'フィッティパルディ'), (0, 'オゼッラ'), (0, 'トールマン')
    ]),
    

あと、コンストラクターのつながりを表現するために、以下のようなリストも用意しました。

constructors_data.py
constructor_connect = [
    (('シャドウ', 1980), ('セオドール', 1981)),
    (('マーチ(1981)', 1982), ('RAM', 1983)),
    (('エンサイン', 1982), ('セオドール', 1983)),
    (('トールマン', 1985), ('ベネトン', 1986)),
    

実装

このデータをPythonにてimportして、graphvizを使って可視化しました。
ソースはgithubに置いてありますが、ポイントと思った点を書いておきます。

※ソース上「コンストラクター」「チーム」がごっちゃになっていますが、ご了承ください

グラフの初期化

constructors_graph.py
    g = Digraph('G', filename='constructors.gv')

    # クラスタ内のノードに対してランク付けさせるのに必要
    g.attr('graph', newrank='true')

    # クラスタ間でエッジ接続させるのに必要
    g.attr('graph', compound='true')

    # ノードのデフォルトはbox
    g.attr('node', shape='box')

今回はコンストラクターごとにクラスタを作ってその中に各年のコンストラクターランキングをノードで表現するため、クラスタ内のノードを横並びに整列させる必要性、ノード間ではなくクラスタ間で矢印を接続したい、順位のノードは矩形で表現したい、ということで、3つ属性設定しています。

年のラベル

constructors_graph.py
    # 年をラベルとして表示
    for year, _ in constructor_ranking:
        g.node(str(year), label=str(year), shape='none')

年のラベルとして表現したいのですが、そのままだと矩形で囲われて出力されてしまうため、shape='none'を指定して、ラベル文字列のみ出力するようにしました。

コンストラクターの枠

constructors_graph.py
        # cluster_チーム名という形でチームごとの枠を作る
        with g.subgraph(name=f'cluster_{name}') as c:
            # 枠の名前はチーム名にしておく
            c.attr(label=name)

subgraphを使ってコンストラクタごとの枠を作っています。
「label=name」でコンストラクタ名をラベルにしています。

各年を示すノード

※上のsubgraphのwith内のコードです。

constructors_graph.py
            node_names = []

            # 年ごとのノードを作る
            for year, rank in rankings:
                node_name = f'{name}_{year}'
                c.node(node_name, label=f'{rank}位' if rank > 0 else '-')
                node_names.append(node_name)

            # 1つずらすことでノード間のエッジを作る
            c.edges(zip(node_names[:-1], node_names[1:]))

「コンストラクタ名_年」というノードを作成し、ラベルを順位にしています。
node_namesは、年次順に['ウィリアムズ_1980', 'ウィリアムズ_1981', 'ウィリアムズ_1982',…]というリストになっているので、「zip(node_names[:-1], node_names[1:])」というところで、ノード間を示すタプル列ができてます。

年ごとに位置を揃える

constructors_graph.py
    for year, rankings in constructor_ranking:
        nodes = ', '.join([f'"{name}_{year}"' for _, name in rankings])
        g.body.append('{rank=same; ' + f'"{year}", {nodes}' + '}')

Graphvizで処理させるDOTファイルには

{rank=same; "ノード1", "ノード2", }

と記載すれば同じ高さに揃えられるのですが、graphvizではその設定ができなさそうなので、「g.body.append」で直接追加しています。

コンストラクタを示すクラスタ間をつなぐ矢印

constructors_graph.py
    for tail, head in constructor_connect:
        g.edge(f'{tail[0]}_{tail[1]}', f'{head[0]}_{head[1]}',
               lhead=f'cluster_{head[0]}', ltail=f'cluster_{tail[0]}')

コンストラクタの名前が切り替わるタイミングでの矢印はノード間ではなくて、クラスタ間で結びたいので、「lhead」「ltail」でクラスタ名を指定しています。

最後に

とりあえず1980年から2017年までの変遷を見られるようにしてみました。
1980年からにしたのは、それ以前だとプライベーターが多い関係で、繋がりを追うのがしんどくなってきたため。

実際に可視化して、私が見始めた1987年から何らかの形でつながっているチームは…と見てみると、ティレル(→BAR→ホンダ→ブラウン→メルセデス)、フェラーリ、ウィリアムズ、ベネトン(→ルノー→ロータス→ルノー)、ミナルディ(→トロ・ロッソ)、マクラーレン。
フェラーリのような名門が残っている一方で、どちらかというと常時真ん中より後ろ、という位置だったミナルディの流れが今も続いている、というのが面白いなぁ…なんて思いました。

masaminh
40代エンジニアです。AWSソリューションアーキテクト-アソシエイト。 業務ではC#/C++中心ですが、趣味で書くプログラムはPythonで書いてます。 最近Node.js始めました。
http://masaminh.oumanoshasin.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした