import%20marimo%0A%0A__generated_with%20%3D%20%220.18.4%22%0Aapp%20%3D%20marimo.App(width%3D%22full%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20%23%20marimo%20notebook%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20%23%20object%20handling%0A%20%20%20%20import%20attrs%0A%0A%20%20%20%20%23%20data%20handling%0A%20%20%20%20import%20polars%20as%20pl%0A%0A%20%20%20%20%23%20plotting%0A%20%20%20%20import%20seaborn%20as%20sns%0A%0A%20%20%20%20%23%20pinsdb%20module%0A%20%20%20%20from%20pinsdb.models%20import%20Game%0A%20%20%20%20from%20pinsdb.namespace.expressions%20import%20Bowling%20%20%23%20noqa%3A%20F401%0A%20%20%20%20return%20Game%2C%20attrs%2C%20mo%2C%20pl%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20**Load%20Games**%0A%20%20%20%20---%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(Game%2C%20attrs%2C%20pl)%3A%0A%20%20%20%20%23%20define%20bowlers%20to%20include%20in%20%60bowler_frame%60%0A%20%20%20%20BOWLERS%3A%20tuple%5Bstr%5D%20%3D%20(%0A%20%20%20%20%20%20%20%20%22Alek%22%2C%0A%20%20%20%20%20%20%20%20%22Cam%22%2C%0A%20%20%20%20%20%20%20%20%22Jake%22%2C%0A%20%20%20%20%20%20%20%20%22Lucas%22%2C%0A%20%20%20%20%20%20%20%20%22Munson%22%2C%0A%20%20%20%20%20%20%20%20%22Ryley%22%2C%0A%20%20%20%20%20%20%20%20%22Spencer%22%2C%0A%20%20%20%20%20%20%20%20%22Tristan%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20games%20stored%20as%20a%20Python%20object%0A%20%20%20%20all_games%20%3D%20sorted(%0A%20%20%20%20%20%20%20%20Game.load_games(verbose%3DFalse)%2C%0A%20%20%20%20%20%20%20%20key%3Dlambda%20g%3A%20(g.date%2C%20g.game_id%2C%20g.bowler.bowler_id)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20games%20stored%20as%20a%20Polars%20DataFrame%0A%20%20%20%20bowler_frame%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.DataFrame(%5Battrs.asdict(game)%20for%20game%20in%20all_games%5D)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22bowler%22).struct.field(%22bowler_id%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22throws%22).bowling.compute_score()%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22date%22%2C%20%22game_id%22)%0A%20%20%20%20%20%20%20%20.drop(%22bowler%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22bowler_id%22).is_in(BOWLERS))%0A%20%20%20%20)%0A%20%20%20%20return%20all_games%2C%20bowler_frame%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(bowler_frame%2C%20pl)%3A%0A%20%20%20%20import%20polars.selectors%20as%20cs%0A%0A%20%20%20%20n_recent_games%3A%20int%20%3D%2050%0A%20%20%20%20col_recent_games%3A%20str%20%3D%20f%22Recent%20Games%20(%7Bn_recent_games%3A%2C%7D)%22%0A%0A%20%20%20%20frames_data%20%3D%20(%0A%20%20%20%20%20%20%20%20bowler_frame.with_columns(pl.col(%22throws%22).bowling.construct_frames())%0A%20%20%20%20%20%20%20%20.explode(%22frames%22)%0A%20%20%20%20%20%20%20%20.drop(%22throws%22)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22frames%22).bowling.is_gutter()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22frames%22).bowling.is_strike()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22frames%22).bowling.is_spare()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22frames%22).bowling.is_wombat()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22frames%22).bowling.is_open()%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20summary_detection_table%20%3D%20(%0A%20%20%20%20%20%20%20%20frames_data.group_by(%22bowler_id%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22frames%22).count().alias(%22Frames%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_strike%22).sum().alias(%22Strikes%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_spare%22).sum().alias(%22Spares%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_open%22).sum().alias(%22Open%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_wombat%22).sum().alias(%22Wombats%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_gutter%22).sum().alias(%22Gutters%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22Strikes%22%2C%20%22Spares%22%2C%20%22Wombats%22%2C%20descending%3DTrue)%0A%20%20%20%20)%0A%0A%20%20%20%20summary_statistics_table%20%3D%20(%0A%20%20%20%20%20%20%20%20bowler_frame.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22throws%22).list.sum().alias(%22pins%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.group_by(%22bowler_id%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22pins%22).count().alias(%22Games%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22pins%22).sum().alias(%22Pins%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22score%22).sum().alias(%22Points%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22score%22).min().alias(%22Lowest%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22score%22).max().alias(%22Highest%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22score%22).tail(n_recent_games).alias(col_recent_games)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20summary_table%20%3D%20(%0A%20%20%20%20%20%20%20%20summary_statistics_table.join(summary_detection_table%2C%20on%3D%22bowler_id%22)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22Points%22)%20%2F%20pl.col(%22Games%22)).round(2).alias(%22Points%20Per%20Game%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22Strikes%22)%20%2F%20pl.col(%22Games%22)).round(2).alias(%22Strikes%20Per%20Game%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22Spares%22)%20%2F%20pl.col(%22Games%22)).round(2).alias(%22Spares%20Per%20Game%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22Wombats%22)%20%2F%20pl.col(%22Games%22)).round(2).alias(%22Wombats%20Per%20Game%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22Gutters%22)%20%2F%20pl.col(%22Games%22)).round(2).alias(%22Gutters%20Per%20Game%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22Points%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22bowler_id%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Games%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Frames%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Pins%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cs.starts_with(%22Point%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cs.starts_with(%22Strike%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cs.starts_with(%22Spare%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cs.starts_with(%22Wombat%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cs.starts_with(%22Gutter%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20return%20cs%2C%20frames_data%2C%20summary_table%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(all_games%2C%20cs%2C%20summary_table)%3A%0A%20%20%20%20from%20great_tables%20import%20GT%0A%0A%20%20%20%20DATETIME_FORMAT%20%3D%20%22%25b%20%25d%2C%20%25Y%22%0A%20%20%20%20RANGE_START%20%3D%20all_games%5B0%5D.date.strftime(DATETIME_FORMAT)%0A%20%20%20%20RANGE_END%20%3D%20all_games%5B-1%5D.date.strftime(DATETIME_FORMAT)%0A%0A%20%20%20%20class%20Palette%3A%0A%20%20%20%20%20%20%20%20PINS%20%3D%20%22Greens%22%0A%20%20%20%20%20%20%20%20POINTS%20%3D%20%22GnBu%22%0A%20%20%20%20%20%20%20%20STRIKES%20%3D%20%22PuBu%22%0A%20%20%20%20%20%20%20%20SPARES%20%3D%20%22Purples%22%0A%20%20%20%20%20%20%20%20WOMBATS%20%3D%20%22Oranges%22%0A%20%20%20%20%20%20%20%20GUTTERS%20%3D%20%22Reds%22%0A%0A%20%20%20%20gt_table%20%3D%20(%0A%20%20%20%20%20%20%20%20GT(summary_table)%0A%20%20%20%20%20%20%20%20.tab_header(%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Bowling%20Statistics%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20subtitle%3Df%22%7BRANGE_START%7D%20to%20%7BRANGE_END%7D%20%7C%20Sorted%20by%20Total%20Points%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.tab_stub(rowname_col%3D%22bowler_id%22)%0A%20%20%20%20%20%20%20%20.tab_spanner(%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Scoring%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Pins%22)%20%7C%20cs.starts_with(%22Point%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.tab_spanner(label%3D%22Strikes%22%2C%20columns%3Dcs.starts_with(%22Strike%22))%0A%20%20%20%20%20%20%20%20.tab_spanner(label%3D%22Spares%22%2C%20columns%3Dcs.starts_with(%22Spare%22))%0A%20%20%20%20%20%20%20%20.tab_spanner(label%3D%22Wombats%22%2C%20columns%3Dcs.starts_with(%22Wombat%22))%0A%20%20%20%20%20%20%20%20.tab_spanner(label%3D%22Gutters%22%2C%20columns%3Dcs.starts_with(%22Gutter%22))%0A%20%20%20%20%20%20%20%20.data_color(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Pins%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20palette%3DPalette.PINS%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.data_color(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Point%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20palette%3DPalette.POINTS%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.data_color(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Strike%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20palette%3DPalette.STRIKES%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.data_color(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Spare%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20palette%3DPalette.SPARES%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.data_color(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Wombat%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20palette%3DPalette.WOMBATS%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.data_color(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dcs.starts_with(%22Gutter%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20palette%3DPalette.GUTTERS%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.fmt_number(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3D%5B%22Pins%22%2C%20%22Points%22%2C%20%22Frames%22%2C%20%22Strikes%22%2C%20%22Spares%22%2C%20%22Wombats%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20decimals%3D0%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20gt_table%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20**Scoring%20Leaderboards**%0A%20%20%20%20---%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20N_SCORES%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D1%2C%20stop%3D30%2C%20value%3D10%2C%20show_value%3DTrue%2C%20label%3D%22Number%20of%20Scores%22%0A%20%20%20%20).form()%0A%20%20%20%20N_SCORES%0A%20%20%20%20return%20(N_SCORES%2C)%0A%0A%0A%40app.cell%0Adef%20_(N_SCORES%2C%20bowler_frame)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_top_bottom_scores%0A%0A%20%20%20%20plot_top_bottom_scores(bowler_frame%3Dbowler_frame%2C%20n%3DN_SCORES.value%20or%2010)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20**Scoring%20Distributions**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(bowler_frame)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_score_distribution%0A%0A%20%20%20%20plot_score_distribution(bowler_frame%3Dbowler_frame)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(frames_data)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_frame_outcome%0A%0A%20%20%20%20plot_frame_outcome(frames_data%3Dframes_data)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(frames_data)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_strike_vs_spare_conversion%0A%0A%20%20%20%20plot_strike_vs_spare_conversion(frames_data%3Dframes_data)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(frames_data)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_performance_per_frame%0A%0A%20%20%20%20plot_performance_per_frame(frames_data%3Dframes_data)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20**Scoring%20Progressions**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20**Pin%20Sequence**%0A%20%20%20%20---%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(frames_data)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_first_throw_outcomes%0A%0A%20%20%20%20plot_first_throw_outcomes(frames_data%3Dframes_data)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20**Score%20Progression**%0A%20%20%20%20---%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20WINDOW_SIZE%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D1%2C%20stop%3D30%2C%20step%3D1%2C%20value%3D15%2C%20show_value%3DTrue%2C%20label%3D%22Window%20Size%22%0A%20%20%20%20).form()%0A%20%20%20%20WINDOW_SIZE%0A%20%20%20%20return%20(WINDOW_SIZE%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(WINDOW_SIZE%2C%20bowler_frame)%3A%0A%20%20%20%20from%20pinsdb.plot%20import%20plot_personal_bests%0A%0A%20%20%20%20plot_personal_bests(%0A%20%20%20%20%20%20%20%20bowler_frame%3Dbowler_frame%2C%20rolling_window%3DWINDOW_SIZE.value%20or%2010%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
065a880401bfab135a8a5fc613cb8518