BigQueryとPythonによるGithubへのダイビング

18143 ワード


私が働いている職場の大部分は、Googleを含めて、通常、毎年、または、毎年、パフォーマンス評価システムのいくつかの種類を持っている.そして、必ず、これらのシステムの全ては、数字が好きです.私の仕事の多くがオープンで行われるので、私は評価時間が来るとき、私が何をするかについての洞察を得るために、私のgithub用法に飛び込むようになりました.コミットまたはコミットのコードの数は、仕事の影響に直接翻訳されませんが、彼らはしばしば私の時間を費やした私の記憶をリフレッシュするのに役立ちます、私がしてきた仕事と私は、値を提供するために焦点を合わせる仕事の種類.
Githubは、直接使用することができますAPIを持っている間、あなたがダウンロードできるデータセットがありますGH Archive , データもavailable on Google BigQuery 一緒にother public datasets . GATHUBのような大部分のライブAPIは、レート制限やクォータを持ち、問い合わせの際によくインデックス付けされていないかもしれません.
私は、使用するのが好きですBigQuery Python libraries bigqueryにアクセスする.あなたは同様にオンラインコンソールを使うことができます、しかし、私は結果についてスクリプトをすることができることが役に立つとわかります.
!pip install google-cloud-bigquery

ライブラリがBigQueryと対話してインストールされたら、リクエストを作るのはほとんどのPythonistasに精通しています.この仕事のために、私は通常、使用しますcolab , またはローカルJupyter ノートブック.私は、私はクエリを簡単にダイビング、ローカルのデータをフィルタリングし、データの詳細を発見見つけることができます.
閉じるこの動画はお気に入りから削除されていますJupyter Notebook I used to create this post .
Google QueryプロジェクトがBigQueryに接続するために必要なことは注目に値する.それは助けがあるquickstarts オンボードを加速するためにご利用いただけます.また、bigqueryに含まれますGoogle Cloud free-tier , しかし、多くのクエリはサイズが大きく、手当を排出できます.これをオーサリングするのと同様に、月あたりの1 TBの質問は無料です.Githubデータセットからのデータの1ヶ月あたりのクエリは、- 225 GBです.
# Follow these instructions to create a service account key:
# https://cloud.google.com/bigquery/docs/quickstarts/quickstart-client-libraries
# Then, replace the string below with the path to your service account key

export GOOGLE_APPLICATION_CREDENTIALS='/path/to/your/service-account-key.json'

いくつかの変数の設定とBigQueryのインポート


最初にすることは、スクリプトの残りの部分にいくつかの変数を設定することです.bigquery APIは私の知っている必要がありますGOOGLE_CLOUD_PROJECT_ID また、Githubデータセットクエリでは、ターゲットユーザーと日付の範囲を知る必要があります.
GOOGLE_CLOUD_PROJECT_ID = "google-cloud-project-id"
GITHUB_USERNAME = 'crwilcox'
START_DATE = "2020-08-01"
END_DATE = "2020-09-01"

START_DATE_SUFFIX = START_DATE[2:].replace('-', '')
END_DATE_SUFFIX = END_DATE[2:].replace('-', '')

from google.cloud import bigquery

client = bigquery.client.Client(project=GOOGLE_CLOUD_PROJECT_ID)

データセットへの掘削


出発点として、特定のユーザーのすべてのデータを見てみましょう.まずはタイプ別イベントを見てみましょう.
# Gather all events taken by a particular GitHub user
query = f"""SELECT type, event AS status, COUNT(*) AS count
FROM (
  SELECT type, repo.name as repository, actor.login,
         JSON_EXTRACT(payload, '$.action') AS event, payload, created_at
  FROM `githubarchive.day.20*`
  WHERE actor.login = '{GITHUB_USERNAME}' AND
        created_at BETWEEN TIMESTAMP('{START_DATE}') AND
        TIMESTAMP('{END_DATE}') AND
        _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
        '{END_DATE_SUFFIX}'
)
GROUP BY type, status ORDER BY type, status;
"""

コストとクエリサイズの推定


クエリの1 TBがフリー層に含まれている間、bigqueryの多くのデータセットが大きく、それを排出するのは簡単です.ライブラリの最初のテストを実行する方法のクエリのサイズを推定する方法です.実行のコストと同様に効率を考慮するためにRun Run Queryを使うのは賢明です.例えば、私がこの2.5年にわたってこの質問を実行しようとするならば、クエリーサイズは3 TBを超えます、一方、最後の月はおよそ223 GBです.
# Estimating the bytes processed by the previous query.
job_config = bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
query_job = client.query(
    query,
    job_config=job_config,
)

gb_processed = query_job.total_bytes_processed / (1024*1024*1024)
print("This query will process {} GB.".format(gb_processed))


This query will process 222.74764717370272 GB.

クエリの実行とデータの取得


今、このクエリのサイズが評価されている、それを実行することができますPandas dataframe 結果を探索するために使用することができます.
query_job = client.query(query)
result = query_job.result()
result.to_dataframe()

.dataframe tbody tr th:only-of-type {
    vertical-align: middle;
}

.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
種類
ステータス
カウント
0
イベント
なし
605
1
削除イベント
なし
255
2
フォーベント
なし
34
3
ゴルルミベント
なし
2
4
株式会社
"作成済み"
678年
5
発行イベント
"閉じた"
95
6
発行イベント
"オープン"
174
7
発行イベント
再開
2
8
メンバーイベント
"追加"
15
9
パブリックイベント
なし
1
10
プルラステンベント
"閉じた"
678年
11
プルラステンベント
"オープン"
443年
12
プルラステンベント
再開
7
13
PullRequestReview
"作成済み"
582年
14
プッシュイベント
なし
2243
15
releaseイベント
『発表』
90
16
ウォッチイベント
"開始"
61
がある20+ event types それはさらに調査できる.これらのイベントのいくつかは、与えられたユースケースにより興味深いかもしれません.パフォーマンスのレンズを使用していくつかのイベントWatchEvent or GollumEvent , 面白くないかもしれません.しかし、他のイベントは、以下のような関連性の高い質問に答えるために使用できます.
  • どのように多くのリリースが行われている?
  • どのように多くのプル要求がオープンされている?
  • どのように多くの問題が作成されている?
  • どのように多くの問題が閉鎖されている?
  • リポジトリによる統計情報の収集


    私はGitHubとどのように相互作用するかについて考えるとき、私は組織とリポジトリに関して考えます.
    リポジトリによってグループ化するクエリにいくつかの小さな変更を行うことで、いくつかの新しい統計を得ることができます.
    query = f"""
    SELECT repository, type, event AS status, COUNT(*) AS count
    FROM (
      SELECT type, repo.name as repository, actor.login,
             JSON_EXTRACT(payload, '$.action') AS event, payload, created_at
      FROM `githubarchive.day.20*`
      WHERE actor.login = '{GITHUB_USERNAME}' AND
            created_at BETWEEN TIMESTAMP('{START_DATE}') AND
            TIMESTAMP('{END_DATE}') AND
            _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
            '{END_DATE_SUFFIX}'
    )
    GROUP BY repository, type, status ORDER BY repository, type, status;
    """
    
    query_job = client.query(query)
    results = [i for i in query_job.result()]
    
    
    上記のクエリをより正確にすることができますが、Pythonで一度データを分離するほうが簡単です.また、パンダがここで使用されないことに注意してください、しかし、代わりに結果は列挙されて、Pythonオブジェクトとして使われます.
    ここから、より高いレベルの情報を収集することができます.たとえば、ユーザーによってリリースされているか、またはどのように多くのプル要求が作成されているリリース.
    # Releases made
    count = [int(row.count) for row in results
             if row.type == 'ReleaseEvent']
    print(f"{sum(count)} Releases across {len(count)} repos")
    
    # PRs Made
    count = [int(row.count) for row in results
             if row.type == 'PullRequestEvent' and
             row.status == "\"opened\""]
    print(f"{sum(count)} PRs opened across {len(count)} repos")
    
    # PR Comments Left
    count = [int(row.count) for row in results
             if row.type == 'PullRequestReviewCommentEvent']
    print(f"{sum(count)} PR comments across {len(count)} repos")
    
    # Issues Created
    count = [int(row.count) for row in results
             if row.type == 'IssuesEvent' and
             row.status == "\"opened\""]
    print(f"{sum(count)} issues opened across {len(count)} repos")
    
    # Issues Closed
    count = [int(row.count) for row in results
             if row.type == 'IssuesEvent' and
             row.status == "\"closed\""]
    print(f"{sum(count)} issues closed across {len(count)} repos")
    
    # Issue Comments
    count = [int(row.count) for row in results
             if row.type == 'IssueCommentEvent']
    print(f"{sum(count)} issue comments across {len(count)} repos")
    
    # Push Events
    count = [int(row.count) for row in results
             if row.type == 'PushEvent']
    print(f"{sum(count)} pushes across {len(count)} repos")
    
    
    0 Releases across 0 repos
    3 PRs opened across 3 repos
    61 PR comments across 8 repos
    6 issues opened across 3 repos
    2 issues closed across 1 repos
    17 issue comments across 9 repos
    78 pushes across 13 repos
    
    
    だから別のイベントの種類は、各ペイロードをさらに見ている.

    何か別の.


    もちろん、いくつかの生産性の少ない、より面白いものを見つけることができます.たとえば、何回、私は結着修正を犯しましたか?
    # How often do I forget to run the tests before committing?
    query = f"""
      SELECT type, repo.name as repository, JSON_EXTRACT(payload, '$.commits') as commits,
            actor.login, created_at
      FROM `githubarchive.day.20*`
      WHERE actor.login = '{GITHUB_USERNAME}' AND type = "PushEvent"
            AND created_at BETWEEN TIMESTAMP('{START_DATE}') AND
            TIMESTAMP('{END_DATE}') AND
            _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
            '{END_DATE_SUFFIX}'
    """
    
    query_job = client.query(query)
    result = query_job.result()
    df = result.to_dataframe()
    df
    
    
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
    
    .dataframe tbody tr th {
        vertical-align: top;
    }
    
    .dataframe thead th {
        text-align: right;
    }
    
    種類
    倉庫
    コミットする
    ログイン
    創造する
    0
    プッシュイベント
    Pythonのストア
    [ sha ] :"91 d 6580 e 2903 b 8796 d 8 c 6364 b 7 ;
    クスノキ
    2020 - 08 - 13 16 : 53 : 15 + 00 : 00
    1
    プッシュイベント
    Pythonのストア
    [ sha ] :"f 3 bedc 1 efae 4430 c 6853581 1 faff 06 i 513548
    クスノキ
    2020 - 08 - 13 16 : 53 : 18 + 00 : 00
    2
    プッシュイベント
    パイソン・ファイヤーストア
    { sha ":
    クスノキ
    2020 - 08 - 06 02 : 58 : 25 + 00 : 00
    3
    プッシュイベント
    パイソン・ファイヤーストア
    [ sha ] :"afff 842 a 3356 cts 5 b 0342 b 573121 c 12 b 2 d 601 .
    クスノキ
    2020 - 08 - 06 05 : 55 : 58 + 00 : 00
    4
    プッシュイベント
    パイソン・ファイヤーストア
    [ sha ] :"C 93 a 077 d 6 d 6 bc 6 e 3 c 5070 a 3 a 5070 a 3 b 0439
    クスノキ
    2020 - 08 - 06 05 : 57 : 55 00 : 00
    ...
    ...
    ...
    ...
    ...
    ...
    73
    プッシュイベント
    パイソン・ファイヤーストア
    [ sha ] :"B 902 CA 30 ad 17 bc 0 d 51 d 1 b 03494 e 089 ca 08 ...
    クスノキ
    2020 - 08 - 04 17 : 34 : 22 + 00 : 00
    74
    プッシュイベント
    グーグル・ライブラリ
    [ sha ] :"e 963 b 33 ce 8 c 93994 c 640154 d 5 b 965 c 4 e 3 c 8 3 c 8 .
    クスノキ
    2020 - 08 - 07 21 : 10 : 53 + 00 : 00
    75
    プッシュイベント
    Pythonドキュメントサンプル
    [ sha ] :"86 dbbb 504 f 1423 f 7 d 33796 b 53096 c 5 e 285 e
    クスノキ
    2020 - 08 - 12 17 : 28 : 08 + 00 : 00
    76
    プッシュイベント
    Pythonのストア
    [ sha ] :"7122 f 24 d 0049 ecad 4 e 71 cbacb 4 bb b 26 26 b 4 dd 4 ...
    クスノキ
    2020 - 08 - 20 19 : 36 : 16 + 00 : 00
    77
    プッシュイベント
    CRWilcox/ExporsureNotification - Verification - S .
    [ sha ] :"f 087 f 323 d 0558436
    クスノキ
    2020 - 05 - 05 19 : 21 : 51 + 00 : 00
    78行×5列
    最初の結果を見ると、JSONデータの形状をよりよく理解できる.照会することができるメッセージフィールドがあります.
    df["commits"][0]
    
    
    '[{"sha":"ce97f5e939bcdca1c9c46f472f41ead04ac6b2fe","author":{"name":"Chris Wilcox","email":"[email protected]"},"message":"fix: lint","distinct":true,"url":"https://api.github.com/repos/crwilcox/python-firestore/commits/ce97f5e939bcdca1c9c46f472f41ead04ac6b2fe"}]'
    
    
    len(df[df['commits'].str.contains("lint")])
    
    
    それで、先月私のコミットの14 %(11/78)のようです.誰かがテストスイートを最初に実行するには少し良いかもしれない😅

    もう少し生産的に戻る


    私が取り組んでいるプロジェクトの一部Conventional Commits 構文が使用されます.これは私がやっている仕事のタイプのアイデアを提供することができます.今のところ、私は非従来のコミットを無視して、それらをまとめます.
    import json
    from collections import Counter
    
    commit_types = Counter()
    
    types = [
      "fix", "feat", "chore", "docs", "style",
      "refactor", "perf", "test", "revert"
    ]
    for i in range(len(df)):
      commits = df.loc[i, "commits"]
      json_commits = json.loads(commits)
      for commit in json_commits:
        # If the first line contains a : assume the left side is the type.
        found_type = False
        for t in types:
          if commit['message'].startswith(t):
            commit_types[t] += 1
            found_type = True
            break
        else:
          commit_types["non-conventional"] += 1
    
    commit_types
    
    
    Counter({'chore': 21,
             'docs': 3,
             'feat': 14,
             'fix': 26,
             'non-conventional': 107,
             'refactor': 8,
             'test': 2})
    
    
    それは私のコミットのほとんどが修正され、適切な数は雑用であり、次は機能の実装です.残念なことに、期間の間のかなりの数のコミットは従来のコミットでありません、しかし、傾向がそれらのコミットのために類似していると仮定するのはたぶん安全です.

    どのような使用は、このデータを発見することができますか?


    私は、私がGitTubに私の時間を費やす場所と異なる倉庫での私の時間がどのように壊れるかについて見るために啓発することを見つけるけれども、私があなたがこのデータを使うことができたものの表面を削ることを望むことができると思いません.あなたがこのデータセットを使うかもしれない方法に関するいくつかのより多くの考えは、ここにあります.
  • リポジトリのすべてのGithubの問題を取得します.
  • あなたによって作成されたすべてのGithubの問題を取得します.
  • すべての問題コメントを取得します.
  • すべてのPRコメントを得てください.
  • これらのクエリは以下の通りです.あなたが考えるべきである他の役に立つ質問に関する考えがあるならば、自由に感じてください.

    リポジトリのすべてのGithubの問題を取得します。


    GITHUB_ORGANIZATION = 'googleapis' #@param {type:"string"}
    GITHUB_REPOSITORY = 'python-%' #@param {type:"string"}
    
    # Get all GitHub issues in a repository. In the example, a wildcard is used
    # to get all issues 
    
    query = f"""
      SELECT type, repo.name as repository, JSON_EXTRACT(payload, '$.pull_request.title') as title, actor.login, 
             JSON_EXTRACT(payload, '$.action') AS event, JSON_EXTRACT(payload, '$.pull_request.html_url') as url, created_at
      FROM `githubarchive.day.20*`
      WHERE repo.name LIKE '{GITHUB_ORGANIZATION}/{GITHUB_REPOSITORY}' AND type = "PullRequestEvent"
            AND created_at BETWEEN TIMESTAMP('{START_DATE}') AND
            TIMESTAMP('{END_DATE}') AND
            _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
            '{END_DATE_SUFFIX}'
    """
    
    query_job = client.query(query)
    result = query_job.result()
    result.to_dataframe()
    
    

    あなたによって作成されたすべてのGithubの問題を取得します。


    # Get all GitHub issues by this login
    query = f"""
      SELECT type, repo.name as repository, JSON_EXTRACT(payload, '$.issue.title') as title, actor.login,
             JSON_EXTRACT(payload, '$.action') AS event, JSON_EXTRACT(payload, '$.issue.html_url') as url, created_at
      FROM `githubarchive.day.20*`
      WHERE actor.login = '{GITHUB_USERNAME}' AND type = "IssuesEvent"
            AND created_at BETWEEN TIMESTAMP('{START_DATE}') AND
            TIMESTAMP('{END_DATE}') AND
            _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
            '{END_DATE_SUFFIX}'
    """
    
    query_job = client.query(query)
    result = query_job.result()
    result.to_dataframe()
    
    

    すべての問題コメントを取得します。


    # Get all issue comments
    query = f"""
      SELECT type, repo.name as repository, actor.login,
           JSON_EXTRACT(payload, '$.action') AS event, JSON_EXTRACT(payload, '$.issue.html_url') as url, created_at
      FROM `githubarchive.day.20*`
      WHERE actor.login = '{GITHUB_USERNAME}' AND type = "IssueCommentEvent"
          AND created_at BETWEEN TIMESTAMP('{START_DATE}') AND
            TIMESTAMP('{END_DATE}') AND
            _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
            '{END_DATE_SUFFIX}'
    """
    query_job = client.query(query)
    result = query_job.result()
    result.to_dataframe()
    
    

    すべてのPRコメントを得てください。


    # Get all PR comments created by this login
    query = f"""
      SELECT type, repo.name as repository, actor.login,
             JSON_EXTRACT(payload, '$.action') AS event, JSON_EXTRACT(payload, '$.comment.html_url') as url, created_at
      FROM `githubarchive.day.20*`
      WHERE actor.login = '{GITHUB_USERNAME}' AND type = "PullRequestReviewCommentEvent"
            AND created_at BETWEEN TIMESTAMP('{START_DATE}') AND
            TIMESTAMP('{END_DATE}') AND
            _TABLE_SUFFIX BETWEEN '{START_DATE_SUFFIX}' AND
            '{END_DATE_SUFFIX}'
    """
    
    query_job = client.query(query)
    result = query_job.result()
    result.to_dataframe()
    
    
    当初公開https://chriswilcox.dev 2020年9月02日