This file is used to create custom offensive and defensive power ratings based on points scored and allowed. Subscribe to my Substack newsletter, Monte Carlo Football Picks, to learn more about the logic behind these calculations.

Load packages

library(tidyverse)

Load data

results <- read_csv("Week 07/inputs/game_results.csv")
Rows: 272 Columns: 7
-- Column specification -------------------------------------------------------------------------------------------------------
Delimiter: ","
chr (2): Home, Away
dbl (5): Season, Week, Hm, Aw, Past

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(results, 5)

Rearrange data

res_a <- results %>%
  select(Season, Week, Past, Team = Home, Opponent = Away, PF = Hm, PA = Aw)

res_b <- results %>%
  select(Season, Week, Past, Team = Away, Opponent = Home, PF = Aw, PA = Hm)

results2 <- bind_rows(res_a, res_b) %>%
  filter(Past == 1)

head(results2, 5)

Calculate number of games played


game_count <- results2 %>%
  group_by(Team) %>%
  summarise(n = n())
  

head(game_count, 5)

Calculate total PF and PA

team_points <- results2 %>%
  group_by(Team) %>%
  summarize(PF = sum(PF), PA = sum(PA)) %>%
  left_join(game_count, by = "Team") %>%
  mutate(PF_PG = PF/n, PA_PG = PA/n) %>%
  select(Team, PF, PF_PG, PA, PA_PG, n)


head(team_points, 5)

Calculate average points per game

ppg <- (sum(team_points$PF) + sum(team_points$PA))/ (sum(team_points$n)*2)

ppg
[1] 23.90426

Opp’s PF, PA and games played

opp_pts <- results2 %>%
  left_join(team_points, by = c("Opponent" = "Team")) %>%
  rename("Opp_PF" = PF.y, "Opp_PA" = PA.y) %>%
  select(Season:Opponent, Opp_PF, Opp_PA, n) %>%
  group_by(Team) %>%
  summarise(Opp_PF = sum(Opp_PF), Opp_PA = sum(Opp_PA), Opp_n = sum(n))

points_summary <- left_join(team_points, opp_pts, by = "Team") %>%
  mutate(Opp_PF_oth = Opp_PF - PA, Opp_PA_oth = Opp_PA - PF, Opp_n_oth = Opp_n - n)
  
head(points_summary, 5)

Calculate Opp PG averages and summarize data

summary <- points_summary %>%
  mutate(Opp_PF_PG_oth = Opp_PF_oth/Opp_n_oth, Opp_PA_PG_oth = Opp_PA_oth/Opp_n_oth) %>%
  select(Team, PF_PG, PA_PG, Opp_PF_PG_oth, Opp_PA_PG_oth)

head(summary, 5)

Calculate simple ratings for Offense and Defense

simple_ratings <- summary %>%
  mutate(rating1 = ppg + (PF_PG - ppg) + (ppg - Opp_PA_PG_oth),
         rating2 = ppg - ((ppg - PA_PG) - (ppg - Opp_PF_PG_oth))) %>%
  mutate(scaled1 = scale(rating1, center = TRUE, scale = TRUE)) %>%
  mutate(off_rating = 25 + if_else(scaled1 * sd(rating1) > 3.5, 3.5,
                                   if_else(scaled1 * sd(rating1) < -3.5, -3.5,
                                           scaled1 * sd(rating1)))) %>%
  mutate(scaled2 = scale(rating2, center = TRUE, scale = TRUE)) %>%
  mutate(def_rating = 25 + if_else(scaled2 * sd(rating2) > 3.5, 3.5,
                                   if_else(scaled2 * sd(rating2) < -3.5, -3.5,
                                           scaled2 * sd(rating2)))) %>%
  select(Team, off_rating, def_rating)

write.csv(simple_ratings, "Week 07/simple_ratings.csv")

simple_ratings
LS0tDQp0aXRsZTogIlNpbXBsZSBSYXRpbmdzOiAyMDIxIE5GTCBXZWVrIDA3Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQoNClRoaXMgZmlsZSBpcyB1c2VkIHRvIGNyZWF0ZSBjdXN0b20gb2ZmZW5zaXZlIGFuZCBkZWZlbnNpdmUgcG93ZXIgcmF0aW5ncyBiYXNlZCBvbiBwb2ludHMgc2NvcmVkIGFuZCBhbGxvd2VkLiAgU3Vic2NyaWJlIHRvIG15IFN1YnN0YWNrIG5ld3NsZXR0ZXIsIFtNb250ZSBDYXJsbyBGb290YmFsbCBQaWNrc10oaHR0cHM6Ly9tY2ZwLnN1YnN0YWNrLmNvbS8pLCB0byBsZWFybiBtb3JlIGFib3V0IHRoZSBsb2dpYyBiZWhpbmQgdGhlc2UgY2FsY3VsYXRpb25zLg0KDQoNCg0KTG9hZCBwYWNrYWdlcw0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCg0KTG9hZCBkYXRhDQpgYGB7cn0NCnJlc3VsdHMgPC0gcmVhZF9jc3YoIldlZWsgMDcvaW5wdXRzL2dhbWVfcmVzdWx0cy5jc3YiKQ0KDQpoZWFkKHJlc3VsdHMsIDUpDQpgYGANCg0KDQoNClJlYXJyYW5nZSBkYXRhDQpgYGB7cn0NCnJlc19hIDwtIHJlc3VsdHMgJT4lDQogIHNlbGVjdChTZWFzb24sIFdlZWssIFBhc3QsIFRlYW0gPSBIb21lLCBPcHBvbmVudCA9IEF3YXksIFBGID0gSG0sIFBBID0gQXcpDQoNCnJlc19iIDwtIHJlc3VsdHMgJT4lDQogIHNlbGVjdChTZWFzb24sIFdlZWssIFBhc3QsIFRlYW0gPSBBd2F5LCBPcHBvbmVudCA9IEhvbWUsIFBGID0gQXcsIFBBID0gSG0pDQoNCnJlc3VsdHMyIDwtIGJpbmRfcm93cyhyZXNfYSwgcmVzX2IpICU+JQ0KICBmaWx0ZXIoUGFzdCA9PSAxKQ0KDQpoZWFkKHJlc3VsdHMyLCA1KQ0KYGBgDQoNCg0KDQpDYWxjdWxhdGUgbnVtYmVyIG9mIGdhbWVzIHBsYXllZA0KYGBge3J9DQoNCmdhbWVfY291bnQgPC0gcmVzdWx0czIgJT4lDQogIGdyb3VwX2J5KFRlYW0pICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSkNCiAgDQoNCmhlYWQoZ2FtZV9jb3VudCwgNSkNCmBgYA0KDQoNCg0KQ2FsY3VsYXRlIHRvdGFsIFBGIGFuZCBQQQ0KYGBge3J9DQp0ZWFtX3BvaW50cyA8LSByZXN1bHRzMiAlPiUNCiAgZ3JvdXBfYnkoVGVhbSkgJT4lDQogIHN1bW1hcml6ZShQRiA9IHN1bShQRiksIFBBID0gc3VtKFBBKSkgJT4lDQogIGxlZnRfam9pbihnYW1lX2NvdW50LCBieSA9ICJUZWFtIikgJT4lDQogIG11dGF0ZShQRl9QRyA9IFBGL24sIFBBX1BHID0gUEEvbikgJT4lDQogIHNlbGVjdChUZWFtLCBQRiwgUEZfUEcsIFBBLCBQQV9QRywgbikNCg0KDQpoZWFkKHRlYW1fcG9pbnRzLCA1KQ0KYGBgDQoNCg0KDQpDYWxjdWxhdGUgYXZlcmFnZSBwb2ludHMgcGVyIGdhbWUNCmBgYHtyfQ0KcHBnIDwtIChzdW0odGVhbV9wb2ludHMkUEYpICsgc3VtKHRlYW1fcG9pbnRzJFBBKSkvIChzdW0odGVhbV9wb2ludHMkbikqMikNCg0KcHBnDQpgYGANCg0KDQoNCk9wcCdzIFBGLCBQQSBhbmQgZ2FtZXMgcGxheWVkDQoNCmBgYHtyfQ0Kb3BwX3B0cyA8LSByZXN1bHRzMiAlPiUNCiAgbGVmdF9qb2luKHRlYW1fcG9pbnRzLCBieSA9IGMoIk9wcG9uZW50IiA9ICJUZWFtIikpICU+JQ0KICByZW5hbWUoIk9wcF9QRiIgPSBQRi55LCAiT3BwX1BBIiA9IFBBLnkpICU+JQ0KICBzZWxlY3QoU2Vhc29uOk9wcG9uZW50LCBPcHBfUEYsIE9wcF9QQSwgbikgJT4lDQogIGdyb3VwX2J5KFRlYW0pICU+JQ0KICBzdW1tYXJpc2UoT3BwX1BGID0gc3VtKE9wcF9QRiksIE9wcF9QQSA9IHN1bShPcHBfUEEpLCBPcHBfbiA9IHN1bShuKSkNCg0KcG9pbnRzX3N1bW1hcnkgPC0gbGVmdF9qb2luKHRlYW1fcG9pbnRzLCBvcHBfcHRzLCBieSA9ICJUZWFtIikgJT4lDQogIG11dGF0ZShPcHBfUEZfb3RoID0gT3BwX1BGIC0gUEEsIE9wcF9QQV9vdGggPSBPcHBfUEEgLSBQRiwgT3BwX25fb3RoID0gT3BwX24gLSBuKQ0KICANCmhlYWQocG9pbnRzX3N1bW1hcnksIDUpDQpgYGANCg0KDQoNCkNhbGN1bGF0ZSBPcHAgUEcgYXZlcmFnZXMgYW5kIHN1bW1hcml6ZSBkYXRhDQpgYGB7cn0NCnN1bW1hcnkgPC0gcG9pbnRzX3N1bW1hcnkgJT4lDQogIG11dGF0ZShPcHBfUEZfUEdfb3RoID0gT3BwX1BGX290aC9PcHBfbl9vdGgsIE9wcF9QQV9QR19vdGggPSBPcHBfUEFfb3RoL09wcF9uX290aCkgJT4lDQogIHNlbGVjdChUZWFtLCBQRl9QRywgUEFfUEcsIE9wcF9QRl9QR19vdGgsIE9wcF9QQV9QR19vdGgpDQoNCmhlYWQoc3VtbWFyeSwgNSkNCmBgYA0KDQoNCg0KQ2FsY3VsYXRlIHNpbXBsZSByYXRpbmdzIGZvciBPZmZlbnNlIGFuZCBEZWZlbnNlDQpgYGB7cn0NCnNpbXBsZV9yYXRpbmdzIDwtIHN1bW1hcnkgJT4lDQogIG11dGF0ZShyYXRpbmcxID0gcHBnICsgKFBGX1BHIC0gcHBnKSArIChwcGcgLSBPcHBfUEFfUEdfb3RoKSwNCiAgICAgICAgIHJhdGluZzIgPSBwcGcgLSAoKHBwZyAtIFBBX1BHKSAtIChwcGcgLSBPcHBfUEZfUEdfb3RoKSkpICU+JQ0KICBtdXRhdGUoc2NhbGVkMSA9IHNjYWxlKHJhdGluZzEsIGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkpICU+JQ0KICBtdXRhdGUob2ZmX3JhdGluZyA9IDI1ICsgaWZfZWxzZShzY2FsZWQxICogc2QocmF0aW5nMSkgPiAzLjUsIDMuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZfZWxzZShzY2FsZWQxICogc2QocmF0aW5nMSkgPCAtMy41LCAtMy41LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlZDEgKiBzZChyYXRpbmcxKSkpKSAlPiUNCiAgbXV0YXRlKHNjYWxlZDIgPSBzY2FsZShyYXRpbmcyLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpKSAlPiUNCiAgbXV0YXRlKGRlZl9yYXRpbmcgPSAyNSArIGlmX2Vsc2Uoc2NhbGVkMiAqIHNkKHJhdGluZzIpID4gMy41LCAzLjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmX2Vsc2Uoc2NhbGVkMiAqIHNkKHJhdGluZzIpIDwgLTMuNSwgLTMuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZWQyICogc2QocmF0aW5nMikpKSkgJT4lDQogIHNlbGVjdChUZWFtLCBvZmZfcmF0aW5nLCBkZWZfcmF0aW5nKQ0KDQp3cml0ZS5jc3Yoc2ltcGxlX3JhdGluZ3MsICJXZWVrIDA3L3NpbXBsZV9yYXRpbmdzLmNzdiIpDQoNCnNpbXBsZV9yYXRpbmdzDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=