Pythonのインデントは何が正解なのか、BigQueryで調べてみた

これは誰が得をするのかわからないエントリーですが、コーヒーブレイクにどうぞ。

## 発端その1：Pythonのコードスタイル
Pythonのコードスタイルといえば[PEP8](https://www.python.org/dev/peps/pep-0008/)が有名です。スタイルチェックにこれを利用している方も多いのではないでしょうか？こちらでは、インデントに[半角スペース4つ](https://www.python.org/dev/peps/pep-0008/#indentation)使う事がルールとなっています。
> Use 4 spaces per indentation level.

さて、ところがこの半角スペース4つって、ちょっと幅を取りすぎな感じがしないでしょうか？でも天下のPEP8で規定されているので、勝手に数を減らすとPython警察が出動しそうです。ここはこちらの意見をサポートする事例が必要です。

[Chromium Python Style Guide](https://www.chromium.org/chromium-os/python-style-guidelines)は、Chromium OSにコントリビュートするときに使われるスタイルガイドです。こちらのインデントはどうでしょうか？

> Indentation: use 2, not 4, spaces

**半角スペース2つ！** ChromiumならPython警察も許してくれるでしょう。しかしこの半角スペース2つのインデント、どれ位普及しているのでしょうか？普及率をサポート資料として添付できれば、鬼に金棒です。

## 発端その2：Kaggleの乱数シードネタ
先日Kaggleで、[一番使われる乱数シードの数値は何か？](https://www.kaggle.com/residentmario/most-common-random-seeds)というタイトルの投稿がありました。そこではBigQueryでGitHubのコードを分析し、乱数シード値を抽出して統計を取るという事が行われていました。結果は以下です。

![乱数シード統計](https://www.kaggle.io/svf/1998238/7d56a02178dcb3fb5fa67be22daa671a/__results___files/__results___6_0.png)
via [Kaggle](https://www.kaggle.com/residentmario/most-common-random-seeds)

そんな・・[Meaning of Life](https://en.wikipedia.org/wiki/42_(number)#The_Hitchhiker's_Guide_to_the_Galaxy)が0や1ごときに負けるとは・・

さておき、BigQueryにはいくつか[パブリックデータが用意](https://cloud.google.com/bigquery/public-data/)されていて、[GitHubのリポジトリデータ](https://cloud.google.com/bigquery/public-data/github)もその1つです。これを使えば、インデントの数も統計が取れるのではないでしょうか？

# BigQueryでGitHubのリポジトリ解析
先述の通り、BigQueryにはGitHubのリポジトリデータがパブリックデータとして公開されています。ソースコードそのものが入ったテーブルは2TBもあるので、ここはランダムにサンプリングをしてサイズを減らしたテーブルを使います。これは24GBほどあります。

## ソースコードをスキャンするクエリ
BigQueryはインデックスを付けること無く、全てのデータ[^1]をフルスキャンしてクエリの結果を返します。先ほどのKaggleの例を見てみましょう。

```sql
#standardSQL
SELECT
  REGEXP_EXTRACT(content, r'(random_state=\d*|seed=\d*|random_seed=\d*|random_number=\d*)')
FROM
  `bigquery-public-data.github_repos.sample_contents`
```

[REGEXP_EXTRACT](https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#regexp_extract)というのは、正規表現でマッチした文字列を返す関数です。クエリで正規表現が書けるってすばらしいですよね。
ちなみにこのクエリは**23.6GB**をフルスキャンすることになりますが、**8.4秒で終わりました**。

クエリ結果は次のようになります。

| Row | f0_ |
|--:|:--|--:|
| 1 | seed=3690 |   |
| 2 | seed=1 |   |
| 3 | seed=4124 |   |
| 4 | seed= |   |
| 5 | seed= |
| .. | ... |

Kaggleの例では、この後にクエリ結果をPandasに載せ替えて統計を取っているようです。

## Pythonのインデントを調べるクエリ
これも上記のようにソースコードを正規表現でマッチングさせればなんとかなる…でしょうか？実際にはソースコード中にインデントミスしている箇所もあるはずなので、”完全に”そのソースコードのインデントは何々であると言い切るのは難しそうです。
ここでは、「行頭からのスペース数で最小のものを、そのソースコードのインデントスタイルとして抽出」してみることにします。

<font color='#aaa'>もう少し正確にやるなら、行頭からのスペース数の分布を見て、発生回数がある閾値以上のスペース数の内、最小公倍数となるものとかを抽出すれば良さそうですが、今回は面倒なので止めました。</font>

クエリは次のようになります。

```sql
#standardSQL
WITH
  lines AS (
  SELECT
    SPLIT(content, "\n") AS line,
    id
  FROM
    `bigquery-public-data.github_repos.sample_contents`
  WHERE
    sample_path LIKE "%.py" )
SELECT
  space_count,
  COUNT(space_count) AS number_of_occurence
FROM (
  SELECT
    id,
    MIN(CHAR_LENGTH(REGEXP_EXTRACT(flatten_line, r'^\s+'))) AS space_count
  FROM
    lines
  CROSS JOIN
    UNNEST(lines.line) AS flatten_line
  WHERE
    REGEXP_CONTAINS(flatten_line, r'^\s+')
  GROUP BY
    id )
GROUP BY
  space_count
ORDER BY
  number_of_occurence DESC
```

ちょっと複雑なので１つずつ解説します。（SQLが得意な方、ベターな方法があればお教えください）

1. `WITH` clauseで一行毎のソースを抽出するサブクエリを実行
2. `UNNEST`で`Array`になっているソース文字列をFlatten
3. `GROUP BY`で`id`毎（ソースファイル毎）にまとめる
4. `MIN`で1ソースファイル内の最小スペース文字数を取得
5. `GROUP BY`でスペース文字数毎にまとめる
6. `COUNT`で発生数をカウント（１ソースファイルで１カウント）

という感じです。結果を見てみましょう。

| space_count | number_of_occurence | 
|--:|--:|
| 4 | 63121 |   
| 1 | 9441 |   
| **2** | **6725** |   
| 3 | 1102 |   
| 6 | 145 |

…うん、
**知ってた。**

<strike>1はなんでしょうね？複数行コメントかな。</strike>
コメントいただきました。Tabインデントですね。

しかし、半角スペース2もなかなか健闘していると言えるのではないでしょうか？時系列での推移も示せば、上昇傾向がでるかもしれません。

# まとめ
BigQueryすごい。
解析していないデータが眠っているなら、とりあえずBigQueryに入れておくのもよいでしょう。データを入れているだけなら、ストレージのみの課金しか発生しません。クエリは、月1TBまで無料です。

[^1]: 全てのデータというと語弊がありますが、検索対象となるカラムのデータはスキャンされます。実際にクエリでスキャンするサイズはコンソールに表示されます。