投資のためのデータサイエンス

個人の投資活動に役立つデータ分析にまつわる話題を綴ります。

データサイエンスのおすすめオンライン記事(1月2日付)

明けましておめでとうございます。様々な事業主体においてデータを活用して効率化を図ったり戦略的な意思決定に役立てるための科学的アプローチへのニーズはますます高まっています。本年もデータの統計的分析やデータサイエンスに携わる皆さんに少しでも役立つような記事を投稿していく所存です。

再急降下法の仕組み
towardsdatascience.com
「再急降下法」は機械学習で損失関数を最小化するために用いられるアルゴリズムだが、その仕組みをわかりやすく解説している(数式あり)

全てのデータサイエンティストが知っておくべきデータエンジニアリングの4つの基本事項
www.analyticsvidhya.com
データの分析やモデル化の前段階としてデータの獲得・整形といった「データエンジニアリング」の作業が必要になる。ここでは1)データウェアハウスとデータレイク, 2)データの抽出・変換・装填のパイプライン, 3)データのガバナンスと品質, 4)データの規制と倫理, について解説している。

貴方のPythonコードを改善する4つのコツ
betterprogramming.pub
Pythonのプログラムコードの可読性と処理効率を高めるためのコツ。多重代入や条件分岐を1行で表すなど。

機械学習モデルを実装する3つの方法
towardsdatascience.com
ビジネス現場ではモデルを構築したらいかに実装するかが重要となる。ここでは1)Webサービス, 2)バッチ予測, 3)エッジデバイスへの実装, について実例コードで紹介している。

PyHard: データセットの品質を評価し、分類困難なインスタンスを同定するツール
www.datasciencecentral.com
「データの何処が分類精度を下げているのか」や「アルゴリズムの効率がよくなるデータセットの領域はどこか」を知るうえで役立つ新しいPythonライブラリについての解説。

解釈可能な予測・ナウキャストモデルを構築する:DeepXFの概要
www.datasciencecentral.com
予測やナウキャストに適用できるローコードPythonライブラリDeepXFについての紹介。詳細は上部のリンクからGitHubのページを参照する。

Python Dashによるダッシュボードの構築(改訂版)

【注記】最初に投稿したコードはGoogle Colaboratoryでは動作しますが、Dockerではエラーで動作しないものでした。ダッシュボードはデプロイしなければほとんど意味がなく、現状ではDocker環境がデプロイには最もよい方法と考えらるため、Dockerでエラーなく動いたコードに全面的に置き換えました。

(2023-09-15) docker-compose.ymlに不要な記述があったため修正しました。

一か月以上前にPythonのdashライブラリを用いたインタラクティブなデータテーブルの表示のコードについて投稿しました。それからさらに発展し、今度はいくつかのデータテーブルとグラフを組み合わせたダッシュボードの作成に挑戦します。
前回と同じくDashライブラリを用います。ダッシュボードの作成にはdash_bootstrap_components(dbc)を用います。出力されるhtml画面レイアウトの見た目をよくするためには様々なパラメータを指定する必要があります。幸いdbcを用いたダッシュボードの画面設計については数多くのチュートリアルYouTubu動画があり、ここでもそのうちの一つを参考にしました。
前回と同様に、全国都道府県の様々な指標を収録したエクセルファイルからデータを入力して、各種指標をダッシュボードに表示させます。

# ライブラリのインポート
from dash.dependencies import Output, Input, State
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from flask import Flask
import pandas as pd
import dash
import numpy as np
import dash_table
import plotly.graph_objects as go
from dash_table import FormatTemplate
from dash.dash_table.Format import Format

まず最初に、appという「箱」を作ります。これをサーバー上で走らせることになります。

server = Flask(__name__)
app = dash.Dash(server=server, external_stylesheets=[dbc.themes.FLATLY])
app.title = 'Dashboard'

次に、原データのExcelファイルを読み込み、データ整形用の補助変数を作成します。

#データファイルを読み込む
df1 = pd.read_excel('都道府県各種指標.xlsx',sheet_name='data')
df1 = df1.assign(ch_year = df1['年度'].apply(lambda x: str(x)+'年'))
df1 = df1.assign(id_pref = df1['地域コード']+df1['都道府県'])
df1.sort_values('ch_year',ascending=True,inplace=True)
#表示候補の列名のリストを作成する
my_list = df1.columns
to_remove = ['年度','地域コード','都道府県','ch_year','id_pref']
remained_list = [i for i in my_list if i not in to_remove]


次に、データテーブルの作成に関する記述を行っておきます。

# 表示する列を定義する
columns1 = [
    dict(id='都道府県',name='都道府県'),
    dict(id='2007年', name='2007年度'),
    dict(id='2008年', name='2008年度'),
    dict(id='2009年', name='2009年度'),
    dict(id='2010年', name='2010年度'),
    dict(id='2011年', name='2011年度'),
    dict(id='2012年', name='2012年度'),
    dict(id='2013年', name='2013年度'),
    dict(id='2014年', name='2014年度'),
    dict(id='2015年', name='2015年度'),
    dict(id='2016年', name='2016年度'),
    dict(id='2017年', name='2017年度'),
    dict(id='2018年', name='2018年度')
]
# データテーブルを描画する関数を定義する
def create_dash_table(df):
    return dash_table.DataTable(
        data=df.to_dict('records'),
        columns=columns1,
        style_cell={'fontsize':20, 'font-family':'IPAexGothic'},
        style_cell_conditional=[{'if':{'column_id':c},'textAlign':'left'} for c in ['都道府県']],
        style_data={'color':'black','backgroundColor':'white'},
        style_data_conditional=[{'if':{'row_index':'odd'},'backgroundColor':'rgb(220,220,220)'}],
        style_header={'backgroundColor':'rgb(210,210,210)','color':'black','fontWeight':'bold'} )

さて、ここからいよいよ山場であるレイアウトの記述です。基本的に行(dbc.Row)を切った後その中に列(dbc.Col)を切っていきます。

# レイアウトの定義
app.layout = dbc.Container([ 
    dbc.Row(
        dbc.Col(
            html.H2("日本の都道府県の各種指標"), width={'size': 12, 'offset': 0, 'order': 0}), 
        style = {'textAlign': 'center', 'paddingBottom': '1%'}),
    dbc.Row(
        dbc.Col(
            dcc.Loading(
                children=[
                    html.Div(
                        dcc.Dropdown(id='dropdown1-kpi',options=[{'label':i,'value':i} for i in remained_list],value='総人口'),
                        style={'width':'30%','display':'inline-block','margin-right':10}),
                    html.Div(
                        dcc.Dropdown(id='dropdown1-plot',options=[{'label':i,'value':i} for i in ['箱ひげ図','散布図','バイオリン']],value='箱ひげ図'),
                        style={'width':'30%','display':'inline-block','margin-right':10}),
                    dcc.Graph(id='box-plot')
                    ],color='#000000',type='dot',fullscreen=True ))),
    dbc.Row(
        dbc.Col(
            dcc.Loading(
                children=[
                    html.Div(
                        dcc.Dropdown(id='dropdown2-pref',options=[{'label':i,'value':i} for i in df1['都道府県'].unique()],value='東京都'),
                        style={'width':'30%','display':'inline-block','margin-right':10}),
                    html.Div(
                        dcc.Dropdown(id='dropdown2-kpi',options=[{'label':i,'value':i} for i in remained_list],value='総人口'),
                        style={'width':'30%','display':'inline-block','margin-right':10}),
                    html.Div(
                        dcc.Dropdown(id='dropdown2-plot',options=[{'label':i,'value':i} for i in ['棒グラフ','折れ線グラフ']],value='棒グラフ'),
                        style={'width':'30%','display':'inline-block','margin-right':10}),
                    dcc.Graph(id='basic-plot')
                    ],color='#000000',type='dot',fullscreen=True ))),
    dbc.Row(
        dbc.Col(
            dcc.Loading(
                children=[
                    html.Div(
                        dcc.Dropdown(id='dropdown3-kpi',options=[{'label':i,'value':i} for i in remained_list],value='総人口'),
                        style={'width':'30%','display':'inline-block','margin-right':10}),
                    html.Div(id='datatable-paging',children=[])
                    ],color='#000000',type='dot',fullscreen=True )))
])

次に、コールバックと実行関数の記述です。
一番目に分布グラフについての記述です。

# グラフ1(分布グラフ)のコールバックと実行関数の記述
@app.callback(
    Output('box-plot','figure'),
    [Input('dropdown1-kpi','value'),Input('dropdown1-plot','value')])
def update_figure(inval1,inval2):
    filtered_df=df1
    if inval2 == '箱ひげ図':
        fig=px.box(filtered_df,x='ch_year',y=inval1,hover_name='都道府県')
        fig.update_layout(transition_duration=500)
        return fig
    elif inval2 == '散布図':
        fig=px.scatter(filtered_df,x='ch_year',y=inval1,hover_name='都道府県')
        fig.update_layout(transition_duration=500)
        return fig
    else:
        fig=px.violin(filtered_df,x='ch_year',y=inval1,hover_name='都道府県')
        fig.update_layout(transition_duration=500)
        return fig
    return

二番目に基本グラフについての記述です。

# グラフ2(基本グラフ)のコールバックと実行関数の記述
@app.callback(
    Output('basic-plot','figure'),
    [Input('dropdown2-pref','value'),Input('dropdown2-kpi','value'),Input('dropdown2-plot','value')])
def update_figure(inval1,inval2,inval3):
    filtered_df=df1[df1['都道府県']==inval1]
    if inval3 == '棒グラフ':
        fig=px.bar(filtered_df,x='ch_year',y=inval2)
        fig.update_layout(transition_duration=500)
        return fig
    else:
        fig=px.line(filtered_df,x='ch_year',y=inval2)
        fig.update_layout(transition_duration=500)
        return fig

続いてデータテーブルについてのコールバックと実行関数の記述です。

# グラフ3(データテーブル)のコールバックと実行関数の記述
@app.callback(
    Output('datatable-paging','children'),
    [Input('dropdown3-kpi','value')])
def update_table(inval1):
    df502=df1.pivot(index='id_pref',columns='ch_year',values=inval1)
    df502.reset_index(inplace=True)
    df502=df502.assign(都道府県=df502['id_pref'].apply(lambda x: x[6:]))
    return create_dash_table(df502)

最後にサーバを起動します。Docker環境で実行する場合、docker-compose.ymlなどによりポート番号の指定を行います。ブラウザから指定ポートへアクセスして動作を確認します。

if __name__=='__main__':
    app.run_server()


各グラフ要素のドロップダウンリストを変更すると対応する図も変更されます。本コードは、現行のDocker Desktop for Windowsで動作します。またAWS上にデプロイして動作することも確認しています。
尚、Dockerで必要になるdocker-compose.ymlは以下のようにしています。

version: '3.7'
services:
 
  dashboard:
    build: 
      context: ./
    container_name: dash-app_dashboard
    restart: always
    ports:
      - 8050:80

データサイエンスのおすすめオンライン記事(12月4日付)

「実質ゼロコロナ」で安心しかけたのもつかの間、「オミクロン株」の脅威に一喜一憂する日々となってしまった。ギリシャ文字が全部終わってもまだ新たな変異株が出現するのか?そうしたらどう名付けるのか?などど考えてしまう。今年も残すところ一ヶ月。来年こそはコロナに振り回されない一年になってもらいたいと願うばかりである。
今回も統計やPythonプログラミングなどの最近の記事からいくつかピックアップして紹介したい。

最急降下法入門
towardsdatascience.com
Pythonを用いる、機械学習の最適化で用いられる最急降下法チュートリアル記事

Python 3.9の新機能の解説
dev.to
Python 3.9で新たに加わった機能を具体的例題で解説している

線形回帰の実践的ガイド
towardsdatascience.com
線形回帰の入門チュートリアル記事。探索的データ解析(EDA)、Feature Engineeringといったデータ分析の実務面から入る形で解説されている。

よりよい関数を記述するための私のPython実践
towardsdatascience.com
「関数名の付け方」「より少ないコードが良い」など、筆者がおすすめするPython関数の書き方のコツを解説している。

ここ50年間での最も重要な統計学のアイデア
arxiv.org
無料ダウンロード可能なPDF形式の論文へのリンク。「ブートストラップやシミュレーションに基づく推論」「ベイジアン多段階モデル」「探索的データ解析」などの近年開発された統計学の概念が紹介されている。

TF-IDFとコサイン類似度を用いたおすすめ文書の抽出

昨今のECサイトでは、リコメンド機能によりユーザがさらに買いたいと思うような商品を提案して顧客体験を向上させることが一般的になっています。この背景にある技術は、一つには日本語を自動的に品詞に分解して「わかち書き」にする技術(形態素解析)、もう一つは文章間の「類似度」を評価する技術です。ここでは、あるPCユーザが使用しているアプリ名のリストから、関連するソフトウェアの脆弱性に関するニュース記事を抽出するプログラムを書いてみます。

# ライブラリのインポート
import pandas as pd
import numpy as np
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.tokenfilter import POSStopFilter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

Python形態素解析のためのライブラリとしてはmecabjanomeがありますが、janomeの方がより高速なのでここではjanomeを用います。まず形態素解析のためのTokenizerインスタンスを生成し、名詞以外の品詞を読み捨てするように設定します。

tokenizer = Tokenizer()
# 読み捨てるトークンの品詞を指定する
token_filters = [POSStopFilter(['記号','助詞','助動詞','動詞'])]
anal = Analyzer(tokenizer=tokenizer, token_filters=token_filters)

次に、あらかじめスクレイピングで取得しておいたソフトウェアの脆弱性に関するニュース記事のデータを読み込みます。

 # 列'header','body'を持つ記事テキストのcsvファイルを読み込む
 df1 = pd.read_csv('/content/drive/My Drive/Colab Notebooks/Biglobe_articles.csv',encoding='cp932')
 df2 = df1.loc[:,['header','body']]
 df2.head(3)

f:id:nicjps230:20211201230005j:plain
次に、対象テキスト文字列を取り出して形態素解析により分かち書き文を作ります。

df2 = df2.assign(wakati='')
# 対象テキスト文字列を一行ずつ取り出して分かち書きに変換する
for i in range(len(df2)):
  texts_flat = df2.iloc[i,1]
  tokens = anal.analyze(texts_flat)
  df2['wakati'][i] = ' '.join([t.surface for t in tokens])
df3 = df2.dropna(how='all')
df3.head(3)

f:id:nicjps230:20211201230033j:plain
次に、類似度評価計算の準備をします。ユーザが使用しているアプリ名を空白で区切って並べ、最後に「脆弱性」を付け加えた文字列を作成します。

# コサイン類似度のしきい値を定める
sim_thresh = 0.05
# 分かち書きをリストに、ヘッダと本文をNumpy配列に格納する
corpus = df3['wakati'].tolist()
header = np.array(df3['header'].tolist())
body = np.array(df3['body'].tolist())
# ユーザの利用しているアプリケーションを記述した文書を指定する
user_app = ['Google Chrome Microsoft Excel PowerPoint Python Anaconda SAKURA Editor Adobe Acrobat 脆弱性']

最後に、ターゲット文書とのコサイン類似度(ベクトル空間モデルにおいて、文書同士を比較する際に用いられる類似度計算手法)を求め、近い文書を取り出します。まずTF-IDF(Term Frequency, Inverse Document Frequency: 文書中に含まれる単語の重要度を評価する手法の1つ)によりスコアベクトルを作成し、それをもとにコサイン類似度を計算します。

# 結果から取り除くエスケープ文字を指定する
bad_chars = ["\n","\t","\r"]
# ユーザアプリの文書をニュース記事リストの先頭に挿入する
docs = user_app + corpus
# TF-IDFベクトル化する
vectorizer = TfidfVectorizer(max_df=0.9)
X = vectorizer.fit_transform(docs)
# コサイン類似度を計算する
sim = cosine_similarity(X)
# ターゲット文書に関わる一行目だけ残し、最初の要素は自分との比較なので取り除く
simil = sim[0][1:]
# コサイン類似度がしきい値を超えるインデックスを取得する。np.where結果はtuple
relev_index = np.where(simil > sim_thresh)[0]
# 取得したインデックスでヘッダと本文を抽出する
relev_head = header[relev_index]
print(relev_head)
relev_body = body[relev_index]
relev_body_r = []
for s in relev_body:
  relev_body_r.append(''.join((filter(lambda i: i not in bad_chars, s))))
print(relev_body_r)

f:id:nicjps230:20211201230103j:plain
使用しているアプリに関連する脆弱性記事が取り出せました。

Dashによるwebアプリの構築の基礎

Pythonによってデータの分析をして得られたグラフなどのアウトプットについては、画像ファイルに保存して、Word文書やPowerPointのスライドに貼り付けて報告資料にする、というのが最も一般的でしょう。一方、実務ではエンドユーザが自ら分析したい変数や条件を指定してプログラムを動かして結果を得るようにする、すなわちプログラムを実装することが求められる場合も多くあります。
このためPythonにもエンドユーザが直接扱えるwebアプリを構築するためのツールが多数提供されています。ここでは、その中の一つであるDashライブラリを用いて、ユーザが指定したデータ表を出力できるプログラムを作成します。
DashをJupyter Notebook (Google Colaboratory)上で動かすためには、普通のDashの代わりにJupyter Dashライブラリを用います。ここでは全国都道府県の様々な指標を収録したエクセルファイル(データ出典:https://www.nstac.go.jp/SSDSE/)から、ドロップダウンリストで指定された都道府県のデータ表を表示してみます。

# ライブラリのインポート
import numpy as np
import pandas as pd
from jupyter_dash import JupyterDash
import pandas as pd
import numpy as np
import dash_html_components as html
import dash_core_components as dcc
from dash import dash_table as dt
import plotly.graph_objects as go
from dash.dependencies import Input, Output
from dash_table import DataTable, FormatTemplate
from dash_table.Format import Format

まず、原データのExcelファイルを読み込みます。

 df1 = pd.read_excel('/content/drive/My Drive/Colab Notebooks/都道府県各種指標.xlsx',sheet_name='data')
 df1.head(3)

f:id:nicjps230:20211127234202j:plain
このデータフレームの各種指標の列を縦に再展開します。

df501 = pd.melt(df1, id_vars=['年度','都道府県'], value_vars=df1.columns[3:])
df501.head()

f:id:nicjps230:20211127234222j:plain
次に、外部のスタイルシートを読み込み、JupyterDashの「箱」にあたるものと、画面に表示する列を定義します。

# 外部スタイルシートを読み込む
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
# JupyterDashの「箱」を作る
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
# 表示する列を定義する
columns1 = [
    dict(id='年度',name='年度'),
    dict(id='都道府県',name='都道府県'),
    dict(id='variable', name='指標'),
    dict(id='value', name='値')
]

次に、画面に表示するコンテンツとして、ドロップダウンリストとデータテーブルを定義します。ドロップダウンリストでは都道府県と指標を選び、これらをデータテーブルのフィルターとします。各コンテンツにはid=で参照用の名前をつけ、詳細は以下に続くコードで設定します。

# ドロップダウンリストを定義する
content1 = dcc.Dropdown(
    id = 'dropdown-for-pref', # id名はコールバックで必要
    options = [{'label':i,'value':i} for i in df501['都道府県'].unique()], # 値を選択する列を指定
    value = '東京都' # デフォルト値を設定
)
content2 = dcc.Dropdown(
    id = 'dropdown-for-variable', # id名はコールバックで必要
    options = [{'label':i,'value':i} for i in df501['variable'].unique()], # 値を選択する列を指定
    value = '総人口' # デフォルト値を設定
)
# データテーブルを定義する
content3 = html.Div(id='datatable-paging',children=[]) # id名はコールバックで必要

次に、上で定義した2つのコンテンツからなる画面のレイアウトを定義します。

# 画面のレイアウトを定義する
app.layout = html.Div([
                       html.Div(content1, style={'width': '25%', 'display': 'inline-block','margin-right': 10}),
                       html.Div(content2, style={'width': '25%', 'display': 'inline-block','margin-right': 10}),
                       content3
])

次に、データテーブルを描画する関数を定義します。データテーブルは一行おきに色を濃淡に分けて見やすくします。

# データテーブルを描画する関数を定義する
# 一行おきに色のコントラストをつけたデザインにする
def create_dash_table(df):
  return dt.DataTable(
      data=df.to_dict('records'),
      columns=columns1,
      style_cell={'fontsize':20, 'font-family':'IPAexGothic'},
      style_cell_conditional=[
                              {
                                  'if': {'column_id':c},
                                  'textAlign': 'left'
                              } for c in ['年度','都道府県']
      ],
      style_data={
          'color':'black',
          'backgroundColor':'white'
      },
      style_data_conditional=[
                          {
                              'if': {'row_index':'odd'},
                              'backgroundColor': 'rgb(220,220,220)',
                          }
      ],
      style_header={
          'backgroundColor':'rgb(210,210,210)',
          'color':'black',
          'fontWeight':'bold'
  })

続いてコールバックを定義します。ここでは、どこから入力してどこから出力するかを指定します。ここで上でつけたid名を使います。またcomponent_property=でその属性を指定します。コールバックにより起動される関数はその下に空行を挟まずに記述します。関数名は何でもかまいません。ここでは、ドロップダウンリストにより指定された都道府県と指標でデータをフィルターにかけ、データテーブルを再表示しています。

# コールバックを定義する(ドロップダウンリストで選択された値をもとに表を作成する)
# どこから入力し、どこへ出力するかを指定する
@app.callback(
    Output(component_id='datatable-paging',component_property='children'),
    [Input(component_id='dropdown-for-pref',component_property='value'),
    Input(component_id='dropdown-for-variable',component_property='value')]
)
def update_table(input_value1,input_value2): # 行間をあけずに関数部分を書く(関数名は何でも良い。引数は上で定義した入力が順に並ぶ)
  df511 = df501[df501['都道府県'] == input_value1] # 値を選択する列を指定
  df512 = df511[df511['variable'] == input_value2]
  df512.sort_values('年度', ascending=True,inplace=True)
  return create_dash_table(df512)

最後にサーバを起動します。この部分を実行すると、下にリンクのURLが表示され、それをクリックするとアウトプット画面が表示されます。

# サーバーを起動する
app.run_server()

f:id:nicjps230:20211127234318j:plain
上記画面のドロップダウンリストで都道府県と指標を選ぶとその都道府県の指標のデータテーブルが表示されます。
(2011/11/27 複数入力のドロップダウンリスト利用に変更しました)

データサイエンスのおすすめオンライン記事(11月4日付)

コロナ感染者もみるみるうちに「実質ゼロコロナ」といえるほどまで減少した。海外ではまだ多くの感染者を出している国もあり予断を許さないが、混雑した町並みや電車など、日常生活が戻ってきたと実感することが多くなった。
今回紹介する記事もストックにあったもので、夏頃に投稿されたものが多い。

DataSpell: データサイエンスのための新しいIDE
towardsdatascience.com
PyCharmを開発しているJet Brain社が新たに世に送り出すデータサイエンス用IDE。すでに日本語サイトもある。

fast.aiにおける多ラベル分類
towardsdatascience.com
一つの画像に複数の重要オブジェクトがある場合のラベル付けの方法について。
fast.aiは、理論や数学から入るBottom-upアプローチではなく、まずはコードを動かしてタスクを解くTop-downタイプのMOOC(大規模公開オンライン講座

Pythonを用いてGoogle Slidesの図を作る
towardsdatascience.com
Google APIとgslidesパッケージを利用して、Googleスライドを全てPythonで作る方法。

不動産、気象、機械学習における「場所」の重要性
medium.com
Kirk Borne氏のエッセイ。不動産や気象でよく言われる「一に場所、二に場所、三に場所」は機械学習アルゴリズムにもあてはまる。

実生活におけるマルコフモデルマルコフ連鎖
towardsdatascience.com
「マルコフ」は遷移確率が一つ前の状態にのみ依存するものであるが、一般にとっつきにくいこの概念を身近な生活上の例で説明している。

ログデータにおけるテキストデータ連続累積出現回数の算出

様々な活動を記録したログデータにおいて、あるパターンの文字列が何回連続して登場したかをカウントすることが必要になる場合があります。これは集計処理とは異なるため、コーディングは多少ややこしくなります。
ここでは例として、サッカーJリーグの歴代優勝チームのリストから、各年代毎に最多連覇のケースを求めます。

# ライブラリのインポート
import numpy as np
import pandas as pd

まず、Jリーグの歴代優勝チームを記録したデータファイルを読み込みます。

df1 = pd.read_excel('/content/drive/My Drive/Colab Notebooks/サッカーJリーグ.xlsx',sheet_name='winner')
df1.head()

f:id:nicjps230:20211027190938j:plain
次に、年代ごとに年間優勝チームの連続累積出現回数を求めます。
データフレームのあるカラムに対して、連続値の個数をカウントするには、値の変化点にフラグを立てて、cumsum()で階段状の累積和を求めます。 この累積和をグループ番号とすることで、ユニークなグループ化ができます。 あとはgroupbyのcumcount()で各グループの個数をカウントします。

df1.set_index('年代',inplace=True)
df1['win_cum'] = df1.groupby(level=0)['年間優勝'].transform(lambda y:y.groupby((y != y.shift()).cumsum()).cumcount()+1)
df1.head()

f:id:nicjps230:20211027191008j:plain
次に、年代毎に連続累積出現回数が最大の行を抽出します。 最大値タイが複数ケースある場合は複数行抽出されます。

df2 = df1.set_index('年度',append=True)
df3 = df2[df2.groupby(level=0)['win_cum'].apply(lambda x: x==x.max())]
df3

f:id:nicjps230:20211104102307j:plain

尚、2010年のJリーグ優勝チームは名古屋グランパスであり、年代で分けない場合でも鹿島アントラーズは三連覇で止まっています。
※2021年のJリーグ結果をふまえて更新しました。