And Now For Michael’s Programming Bytes 2025-26 NBA Season Predictions

Advertisements

Hello everybody,

Michael here, and in today’s post we will discuss what I think is the fun part about this NBA post prediction series-the predictions themselves.

That’s right, now that we have our model, let’s make some predictions for the season!

Now where did we leave off?

Before we get into the juicy NBA season predictions, let’s first revisit where we left off on the previous post Another Crack At Linear Regression NBA Machine Learning Predictions (2025-26 edition):

Towards the end of the previous post, we generated this equation to assist us in generating our linear regression NBA season predictions for this year. To recap what the equation means:

  • 64.8 * (field goal %)
  • PLUS 113 * (3-point %)
  • PLUS 15.4 * (2-point %)
  • MINUS 1.94 * (seeding at end of season)
  • PLUS 0.011 * (total rebounds)
  • MINUS 0.00346 * (total assists)
  • PLUS 0.0215 * (total steals)
  • PLUS 0.00663 * (total blocks)
  • MINUS 0.0097 * (total turnovers)
  • MINUS 60.38 (the intercept)

That’s quite a mouthful, but I’ll show you the Python calculations we’ll be doing in order to generate those juicy predictions!

  • I’ll admit that even I’m not perfect with my blogs here, as I made a small mistake on the previous post that showed part of the equations as 215 * (total steals) rather than 0.0215 * (total steals). As it turns out, even experienced coders like me make oversights, so apologies for that!

A little disclaimer here

Before we dive in to our predictions, I want to clarify that these are simply win total/conference seeding predictions based off of a simple linear regression model configured by me. I personally wouldn’t use these predictions for any bets or parlays because first and foremost, I am your friendly neighborhood coding blogger, not your friendly neighborhood sportsbook. You can count on me for juicy, way-too-early predictions, but certainly not for any juicy over/unders.

If you do bet on NBA games this season, please do so responsibly! Thank you!

The way of the weighted averages

You may recall that for my post on last NBA season’s predictions, we used weighted averages to help generate the predictions. Since I personally liked that method, I’ll do so again.

Here’s the file with the weighted averages, which we’ll be using to calculate the predictions:

We’ll use the same methodology as we did last year for calculating the weighted averages, which went like this:

  • 2022-23 to 2024-25 (last 3 seasons)-0.2 weight (higher weight for the three most recent seasons)
  • 2019-20 to 2021-22 (three seasons prior to that)-0.1 (less weight for seasons further in the past, plus this timespan does include the two COVID shortened seasons)
  • 2015-16 to 2018-19 (four seasons further back)-0.025 (even less weight for these seasons further in the past)

Now here’s the weighted averages file for all 30 teams:

So, without further ado, let’s predict some win totals!

import pandas as pd

NBAAVG = pd.read_csv(r'C:\Users\mof39\OneDrive\Documents\weighted averages 2025-26.csv')

for n in NBAAVG['Team']:
    print(64.8*NBAAVG['FG%'] + 113*NBAAVG['3P%'] + 15.4*NBAAVG['2P%'] - 1.94*NBAAVG['Finish'] + 0.011*NBAAVG['TRB'] - 0.00346*NBAAVG['AST'] + 0.0215*NBAAVG['STL'] + 0.00663*NBAAVG['BLK'] - 0.0097*NBAAVG['TOV'] - 60.38)
    break

0     40.21900 (Atlanta Hawks)
1     52.98400 (Boston Celtics)
2     37.83554 (Brooklyn Nets)
3     34.54740 (Charlotte Hornets)
4     40.36300 (Chicago Bulls)
5     50.23470 (Cleveland Cavaliers)
6     43.38590 (Dallas Mavericks)
7     50.17750 (Denver Nuggets)
8     33.35420 (Detroit Pistons)
9     45.65995 (Golden State Warriors)
10    41.07520 (Houston Rockets)
11    45.93936 (Indiana Pacers)
12    51.35416 (LA Clippers)
13    45.88495 (LA Lakers)
14    47.79176 (Memphis Grizzlies)
15    41.26986 (Miami Heat)
16    48.55712 (Milwaukee Bucks)
17    47.12266 (Minnesota Timberwolves)
18    40.65833 (New Orleans Pelicans)
19    47.90818 (NY Knicks)
20    58.57943 (Oklahoma City Thunder)
21    41.25042 (Orlando Magic)
22    43.73122 (Philadelphia 76ers)
23    45.39194 (Phoenix Suns)
24    39.99757 (Portland Trail Blazers)
25    43.89877 (Sacramento Kings)
26    39.38897 (San Antonio Spurs)
27    39.13594 (Toronto Raptors)
28    40.82412 (Utah Jazz)
29    36.85122 (Washington Wizards)

Once I read the weighted averages CSV and ran the equation for all 30 teams, I get the predicted win totals for all 30 teams, which I will use for my way-too-early East/West seeding chart. Note that since the team names aren’t shown in the output, I took the liberty of manually adding each team name by each predicted win total so you know your favorite team’s projected win total (according to my model, of course).

One interesting difference between this year’s projected win totals and last year’s is the narrower range of possible win totals in this year’s model. See, the range of possible win totals in last year’s model was 24-54 wins, while the range of possible win totals in this year’s model is just 33-59 wins. Could the narrower possible win total range be due to the different features I used in this year’s model? It’ll be interesting to see how the season plays out.

Another interesting thing to note is that even though there is a narrower range of potential wins in this year’s model, the majority of teams’ win counts last season fell into this range-20 teams won between 33 and 59 games last season (Knicks, Pacers, Bucks, Pistons, Magic, Hawks, Bulls, Heat, Rockets, Lakers, Nuggets, Clippers, Timberwolves, Warriors, Grizzlies, Kings, Mavericks, Suns, TrailBlazers and Spurs).

How will the win counts look this time around? We’ll see as the season unfolds!

Michael’s Way-Too-Early Conference Seeding:

And now, for the stuff I really wanted to share with you all in this post: Michael’s Way-Too-Early Conference Seeding. Now that we’ve got our projected win totals for each team, it’s time to seed them in their projected spots! But that’s not all I’m going to do!

In addition to the model’s projected seedings, I’ll also give you my own personal seedings for all 30 teams. That’s right-this year, I want to see which set of predictions comes out more accurate-my predications or my model’s predictions. This will be fun to revisit next July once the season wraps up!

Eastern Conference predictions

To begin, let’s start with the model’s Eastern Conference predictions:

Play-OffsPlay-InsMaybe Next Year
1. Boston Celtics7. Miami Heat11. Toronto Raptors
2. Cleveland Cavaliers8. Orlando Magic12. Brooklyn Nets
3. Milwaukee Bucks9. Chicago Bulls13. Washington Wizards
4. New York Knicks10. Atlanta Hawks14. Charlotte Hornets
5. Indiana Pacers15. Detroit Pistons
6. Philadelphia 76ers

And now, let’s see my personal Eastern Conference predictions:

Play-OffsPlay-InsMaybe Next Year
1. New York Knicks7. Orlando Magic11. Toronto Raptors
2. Cleveland Cavaliers8. Milwaukee Bucks12. Philadelphia 76ers
3. Boston Celtics9. Atlanta Hawks13. Brooklyn Nets
4. Detroit Pistons10. Chicago Bulls14. Charlotte Hornets
5. Miami Heat15. Washington Wizards
6. Indiana Pacers

Here are some interesting observations about both the model’s predictions and my own personal predictions:

  • The Eastern Conference teams that made last season’s play-in (Heat, Hawks, Bulls, Magic) are the same ones projected to make another go at play-ins this year. In other words, could we see the same teams stuck in another year of play-ins?
  • Personally, I think the Hawks, Bulls and Magic will make another trip to the play-in. On the other hand, I think the Heat will eke out a 5 (maybe 6) seed in the East because of some great new acquisitions like small forward Simone Fontecchio and shooting guard Norman Powell.
  • I honestly don’t know why the model hates the Detroit Pistons, as it placed them at the bottom of the East once more. I ranked them as a possible 4-seed because after their improvement last year (44-38 from a dismal 14-68 in 2023-24), I feel they could be quite the playoff contender-and it was certainly nice to see 2021 1st Overall Pick Cade Cunningham finally develop into a star-quality player. The acquisition of the former Heat small forward Duncan Robinson should be exciting to see.
  • This might sound like a hot take here, but I don’t think the Sixers will even qualify for play-in, let alone playoffs given the plethora of issues they had last season. Least of all, Paul George and Joel Embiid-two of the biggest Sixers names-weren’t at the top of their game last season when they were healthy (and both of them missed significant time due to injuries).
  • Unlike my model, I think the Knicks could really take the top spot in the East this season. Despite falling just short of the 2025 NBA Finals, the Knicks showed they can certainly make a deep playoff run with talent such as Jalen Brunson (winner of the Clutch Player of the Year award), OG Anunoby and their acquisition of Karl-Anthony Towns from the Timberwolves during the 2024 offseason.
  • With two of the biggest names in the East-Jayson Tatum and Tyrese Haliburton-out for most if not all of this season due to Achilles injuries they got during last season’s playoffs, I think the East is wide open. Granted, I still think the Pacers and Celtics have a good chance at making the playoffs this year, but I don’t think either of them is a shoo-in for the top spot in the East, which in my opinion leaves the East playoff race wide open for another team to take the top spot (which as I said earlier, I think it could be the Knicks’ year to do just that). Also, I still think the Celtics could realistically clinch the 3-seed in the East despite the offseason departures of Jrue Holiday, Kristaps Porzingis, Al Horford and Luke Kornet, who were all key players in the Celtics 2024 Championship run.

Western Conference predictions:

First, let’s start with how the model think the Western Conference standings will play out this season:

Play-OffsPlay-InsMaybe Next Year
1. Oklahoma City Thunder7. Golden State Warriors11. Houston Rockets
2. LA Clippers8. Phoenix Suns12. Utah Jazz
3. Denver Nuggets9. Sacramento Kings13. New Orleans Pelicans
4. Memphis Grizzlies10. Dallas Mavericks14. Portland Trail Blazers
5. Minnesota Timberwolves15. San Antonio Spurs
6. LA Lakers

Just as with the model’s Eastern Conference predictions, I certainly have disagreements with the Western Conference predictions. Here’s how I think the Western Conference standings will play out this season:

Play-OffsPlay-InsMaybe Next Year
1. Oklahoma City Thunder7. Golden State Warriors11. Dallas Mavericks
2. Houston Rockets8. LA Clippers12. Memphis Grizzlies
3. Minnesota Timberwolves9. Sacramento Kings13. Utah Jazz
4. Denver Nuggets10. San Antonio Spurs14. Portland Trail Blazers
5. Houston Rockets15. New Orleans Pelicans
6. LA Lakers

As I did with my Eastern Conference predictions, here are some interesting observations between the model’s projected conference standings and my personal projected conference standings:

  • I’m sure the question on every NBA fan’s mind-including mine-is “Can the Oklahoma City Thunder pull off another championship?”. My guess-I think of all the champions we’ve seen in the 2020s alone, I think they’ve got the best shot at a repeat title. Why might that be? One big reason that could happen-the Thunder kept their core Big 3 (SGA, Chet Holmgren, and Jaylin Williams) around along with several other key players from the championship run such as Isaiah Hartenstein, Lu Dort, among others. Personally, I think that NBA teams would be wise not to go full rebuild-mode after winning their first championship, and it seems the Thunder have done just that (they only traded second-year small forward Dillon Jones, who played limited minutes in OKC’s championship run). Even if the Thunder don’t end up repeating as champions, I think, at the very least, the 1-seed in the West could be theirs for the taking once more.
  • Another interesting Western Conference storyline to watch would be whether Cooper Flagg (the 2025 #1 overall pick) becomes the next Luka Doncic for the Mavericks. After Doncic got traded for Anthony Davis during last year’s midseason trades, it’s safe to say the Mavericks’ season went south. A controversial trade and injuries to many key players-Anthony Davis (after the trade) and Kyrie Irving being the two most notable examples-didn’t help matters. Then again, having such an injury-struck roster to the point where the Mavericks nearly (but thankfully didn’t) have to forfeit games only added to their problems last season after the infamous Doncic-Davis trade. The drafting of 6’9″, 18-year-old forward Cooper Flagg could bring a spark to the struggling Mavericks (and from watching some of his highlights, I think Flagg has potential), but I think Flagg will need at least a year to gel with the Mavericks before they once again become Western Conference contenders.
  • Just as I was surprised that my model placed the Detroit Pistons at the bottom of the Eastern Conference given their improvements last season, I can say I’m just as surprised that the San Antonio Spurs were placed at the bottom of the Western Conference. Granted, they haven’t made the playoffs since 2019 and just went through a coaching change (Popovich stepped down and Mitch Johnson was named as head coach after serving as interim last season), but they did also improve their record from 22-60 in ’23-’24 to 34-48 last season. The Spurs also have their own solid Big 3 in De’Aaron Fox, Stephon Castle, and of course 2023 #1 overall pick Victor Wembanyama. Even though Wemby’s season was cut short last year due to deep vein thrombosis (a type of blood clot), his improved shooting and double-doubles could certainly help the Spurs once he’s fully recovered.
  • How might the Golden State Warriors do with their 35-and-over Big 3 (Jimmy Butler is 36, Draymond Green is 35, and Steph Curry is 37)? Given that they earned their playoff spot last season through play-ins, I’ve got a hunch that the Warriors might be seeing the play-ins once more-but will likely get a playoff spot in this manner. Yes, they had quite the herky-jerky trajectory last season, but the midseason acquisition of Jimmy Butler certainly gave them an extra spark down the regular season stretch-Butler’s basketball skills certainly paired well with guys like Steph and Draymond. Upsetting the 2-seeded Houston Rockets in the Western Conference quarterfinals last season certainly helps the Warriors’ momentum heading into this season, but I do wonder how the loss of their championship-winning forward Kevon Looney would affect the Warriors dynamic.
  • I know I said that I think the Thunder have a great chance to repeat as champions, but I also wonder if the Timberwolves would be a team to look out for in the 2026 postseason. After all, despite losing franchise mainstay Karl-Anthony Towns to the Knicks in the 2024 offseason, the Timberwolves adapted quite well as stars like Anthony Edwards and Naz Reid rose to the challenge by helping the team get to the Western Conference finals for the second year in a row (even though they got knocked out at the Western Conference finals for the second year in a row too). All in all, in terms of every NBA trade ever made, I think the Karl-Anthony towns trade-along with the players the Timberwolves got in exchange (Julius Randle and Donte DiVincenzo)-was one of the most even trades for both teams involved, as both the Knicks and Timberwolves made it to their respective conference finals.
  • Just as with my play-in predictions for the Eastern conference, at least three of the four projected play-in teams (according to the model) for the Western Conference made the play-ins last season-the Mavericks, Warriors, and Kings. I think the Warriors have the best shot at cracking the actual playoffs while the Mavericks could use another year for Cooper Flagg to develop (plus buy some time to get stars like Kyrie Irving back). It will be interesting to see how the Sacramento Kings fare because even though Domantis Sabonis, Zack LaVine and DeMar DeRozan fared well despite the disappointing finish, the talent around them could use some improvement. Perhaps the addition of Russell Westbrook (who’s in his 18th year in the NBA) could spice up the Kings’ offense, as he certainly showed he still had the athleticism and speed needed for basketball last season with the Denver Nuggets.

And now for something a little scandalous…

Boy oh boy this is certainly going to be the most interesting (or at least the most interestingly-timed) post I’ve written during this blog’s run. Why might that be?

Well, last Thursday (October 23, 2025) news broke that the FBI (US Federal Bureau of Investigation) had arrested 34 people for a pair of scandals that certainly rocked pro basketball-one involving colluding with Italian Mafia families (specifically the Gambino, Bonnano and Genovese crime families) to conduct a series of rigged poker games and another involved colluding to rig sports betting.

Here’s the wildest part though-among the 34 arrested were the current head coach of the Portland Trail Blazers (Chauncey Billups), a current Miami Heat star (Terry Rozier), and a former Cavaliers player (Damon Jones). Billups and Rozier were placed on leave by their respective teams.

Want to know some other juicy, scandalous details? Here are a few takeaways from the indictments:

  • Chauncey Billups was allegedly used by these Mafia families to lure in victims to the rigged poker games in order to make the poker games appear legitimate.
  • How the poker games were rigged is possibly the wildest part, with everything that was alleged to have happened sounding like it could’ve come from a James Bond movie. Among the methods used to rig these poker games were X-Ray tables that allowed these Mafia families to see opponents’ hands and rigged shuffling machines that could be used to predict what opponents’ hands would look like.
  • As for Rozier, the game that led to him being investigated was a March 23, 2023 game while Rozier was still with the Charlotte Hornets. In this game, Rozier left the game early due to a “foot injury”-which wasn’t true as Rozier conspired with a longtime friend of his that he planned to fake the “foot injury” in order to net this friend over $200,000 on his “under” statistics (that Rozier would underperform in the game in other words).
  • As for Damon Jones, he sold insider information to his co-conspirators during the 2022-23 season while working for the Lakers. The information concerned insider tips on lineup decisions and injury reports on star Lakers players; the co-conspirators were able to place significant wagers on their bets with this information. It was later revealed that one of the players whose injury report was leaked was LeBron James, who hasn’t been implicated in any wrongdoing.

All in all, it will be interesting to see how this scandal plays out-especially to see if anyone else get busted as part of this massive gambling ring. Here’s an October 23, 2025 release from the US DOJ (Department of Justice) describing the basics of the gambling ring (keep in mind that anyone involved is presumed innocent until proven guilty)-https://www.justice.gov/usao-edny/pr/current-and-former-national-basketball-association-players-and-four-other-individuals.

Here’s a snippet of a conference from FBI Director Kash Patel on October 23, 2025 regarding the charges-https://www.youtube.com/shorts/4F4_JMGVJXw.

All I will say is that it will be very very interesting to see not only how the rest of the NBA season plays out but also to see how commissioner Adam Silver will change league gambling policy-especially when it comes to players and coaching staff. Assuming other players and/or coaching staff get busted in the gambling ring (which could happen) the trials will be interesting-mostly because we’ll get to see who will snitch on who to get a sweet plea deal. Maybe there will be some RICO charges in the mix-which given what occurred, isn’t a stretch to think.

Anyway, thanks for reading as always, and enjoy the juicy action of the 2025-26 NBA season! The season is still young, so it’s anyone’s game!

Michael

Another Crack At Linear Regression NBA Machine Learning Predictions (2025-26 edition)

Advertisements

Hi everybody,

Michael here, and in today’s post, I thought I’d try something a little familiar. You may recall that last October, I released a pair of posts (Python, Linear Regression & An NBA Season Opening Day Special Post and Python, Linear Regression & the 2024-25 NBA season) attempting to predict each NBA team’s win total and conference seeding based off of their performance from the previous 10 seasons.

All in all, after seeing how the season played out-I managed to get only 3/30 teams in the correct seeding. So what would I do here?

I’ll give my ML NBA machine learning predictions another go, also using data from the previous 10 seasons (2015-16 to 2024-25). You may be wondering why I’m trying to predict the outcomes of the upcoming NBA season once more given how off last year’s predictions were-the reason I’m giving the whole “Michael’s NBA crystal ball” thing another go is because I’m not only interested in how my predictions change from one season to the next but also because I plan to use a slightly different model than I did last year (it’ll still be good old linear regression, however) so I can analyze how different factors might play a role in a team’s record and ultimately their conference seeding.

So, without further ado, let’s jump right in to Michael’s Linear Regression NBA Season Predictions II!

Reading the data

Before we dive in to our juicy predictions, the first thing we need to do is read in the data to the IDE. Here’s the file:

Now let’s import the necessary packages and read in the data!

import pandas as pd
from sklearn.model_selection import train_test_split
from pandas.core.common import random_state
from sklearn.linear_model import LinearRegression
from google.colab import files
uploaded = files.upload()
import io

NBA = pd.read_excel(io.BytesIO(uploaded['NBA analysis 2025-26.xlsx']))
NBA.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 300 entries, 0 to 299
Data columns (total 31 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Season  300 non-null    object 
 1   Team    300 non-null    object 
 2   W       300 non-null    int64  
 3   L       300 non-null    int64  
 4   Finish  300 non-null    int64  
 5   Age     300 non-null    float64
 6   Ht.     300 non-null    object 
 7   Wt.     300 non-null    int64  
 8   G       300 non-null    int64  
 9   MP      300 non-null    int64  
 10  FG      300 non-null    int64  
 11  FGA     300 non-null    int64  
 12  FG%     300 non-null    float64
 13  3P      300 non-null    int64  
 14  3PA     300 non-null    int64  
 15  3P%     300 non-null    float64
 16  2P      300 non-null    int64  
 17  2PA     300 non-null    int64  
 18  2P%     300 non-null    float64
 19  FT      300 non-null    int64  
 20  FTA     300 non-null    int64  
 21  FT%     300 non-null    float64
 22  ORB     300 non-null    int64  
 23  DRB     300 non-null    int64  
 24  TRB     300 non-null    int64  
 25  AST     300 non-null    int64  
 26  STL     300 non-null    int64  
 27  BLK     300 non-null    int64  
 28  TOV     300 non-null    int64  
 29  PF      300 non-null    int64  
 30  PTS     300 non-null    int64  
dtypes: float64(5), int64(23), object(3)
memory usage: 72.8+ KB

As you can see, we’ve still got all 31 features that we had in last year’s dataset-the only difference between this dataset and last year’s is the timeframe covered (this dataset starts with the 2015-16 and ends with the 2024-25 season).

  • Just like last year, this year’s edition of the predictions comes from http://basketball-reference.com, where you can search up plenty of juicy statistics from both the NBA and WNBA. Also, just like last year, the only thing I changed in the data from Basketball Reference is the Finish variable, which represents a team’s conference finish (seeding-wise) as opposed to divisional finish (since divisional finishes are largely irrelevant for a team’s playoff standings).
  • If you want a better explanation of these terms, please feel free to refer to last year’s edition of my predictions post-Python, Linear Regression & An NBA Season Opening Day Special Post.

Now that we’ve read our file into the IDE, let’s create our model!

Creating the model

You may recall that last year, before we created the model, we used the Select-K-Best algorithm to help us pick the optimal model features. For a refresher, here’s what Select-K-Best chose for us:

['L', 'Finish', 'Age', 'FG%', '3P%']

After seeking the five best features for our model from the Select-K-Best algorithm, this is what we got. However, we’re not going to use the Select-K-Best suggestions this year as there are other factors I’d like to analyze when it comes to making upcoming season NBA predictions.

Granted, I’ll keep the Finish, FG%, and 3P% as I feel they provide some value to the model’s predictions, but I’ll also add a few more features of my own choosing:

X = NBA[['FG%', '3P%', '2P%', 'Finish', 'TRB', 'AST', 'STL', 'BLK', 'TOV']]
y = NBA['W']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

Along with the features I chose from last year’s model, I’ll also add the following other scoring categories:

  • 2P%-The percentage of a team’s successful 2-pointers in a given season
  • TRB-A team’s total rebounds in a season
  • AST-A team’s total assists in a season
  • STL-A team’s total steals in a season
  • BLK-A team’s total blocks in a season
  • TOV-A team’s total turnovers in a season

The y-variable will still be W, as we’re still trying to predict an NBA team’s win total for the upcoming season based off of all our x-variables.

Now, let’s create a linear regression model object and run our predictions through that model object:

NBAMODEL = LinearRegression()
NBAMODEL.fit(X_train, y_train)
yPredictions = NBAMODEL.predict(X_test)

yPredictions

array([43.2515066 , 36.4291265 , 55.14626364, 46.01164579, 24.18679591,
       35.59131124, 35.59836527, 49.98114132, 48.57869061, 50.65733101,
       21.296126  , 49.94020238, 31.98306604, 41.89217714, 45.65373458,
       50.57831266, 32.76923727, 45.6898562 , 20.4393901 , 55.28944034,
       52.79027154, 21.81113366, 50.79142468, 50.95798684, 53.23802534,
       50.00199063, 48.4639119 , 49.1671417 , 51.12760913, 31.20606334,
       45.3090483 , 25.02488097, 43.67955061, 48.47484838, 33.74041157,
       41.7463038 , 36.10796911, 40.5399278 , 35.30656175, 16.92677689,
       49.77947698, 39.2160337 , 22.08871355, 31.83549487, 15.2675987 ,
       18.24486804, 21.71657476, 42.21505537, 22.84745758, 25.56862333,
       43.6212702 , 20.28339646, 44.60289296, 49.20316062, 53.69182149,
       29.48304908, 44.60789347, 42.44466633, 55.93637972, 54.89728291])

Just as with last year’s model, the predictions are run on the test dataset, which consists of the last 60 of the dataset’s 300 total records.

And now for the equation…

Now that we’ve generated predictions for our test dataset, let’s find out all of the coefficients and the intercept for the equation I will use to make this year’s NBA predictions:

NBAMODEL.coef_

array([ 6.48260593e+01,  1.13945178e+02,  1.54195451e+01, -1.94822281e+00,
        1.10428617e-02, -3.46015457e-03,  2.15326621e-02,  6.63810730e-03,
       -9.70593407e-03])
NBAMODEL.intercept_

np.float64(-60.37720744829896)

Now that we know what our coefficients are, let’s see what this year’s equation looks like:

Although it’s much more of a mouthful than last year’s equation, it follows the same logic in that it uses the features of this year’s model in the order that I listed them:

['FG%', '3P%', '2P%', 'Finish', 'TRB', 'AST', 'STL', 'BLK', 'TOV']

A is FG%, B is 3P%, and so on until you get to I (which represents TOV).

  • Since all the coefficients are listed in scientific notation, I rounded them to two decimal places before converting them for this equation. Same thing for the intercept.
  • In case you’re wondering, no you can’t add all the coefficients together for this equation as each coefficient plays a part in the overall equation. Just like last year, we’re going to do the weighted-averages thing to generate projected win totals. Keep your eyes peeled for the next post, which covers the juicy predictions.

…and the accuracy test!

So now that we’ve got our 2025-26 NBA predictions model, let’s see how accurate it is:

from sklearn.metrics import mean_absolute_percentage_error

mean_absolute_percentage_error(y_test,yPredictions)

0.09573425883736708

Using the MASE (mean absolute percentage error) from sklearn like we did in last year’s analysis, we see that the model’s margin of error is roughly 9.57%. I’ll round that up to 10%, which means that despite not choosing the model’s features from a prebuilt algorithm, the overall accuracy of the model is still 90%.

Now, whether the model’s accuracy and my predictions hold up is something I’ll certainly revisit in 8 months time for another end-of-season reflection. After all, last season I only got 3 of the 30 teams in the correct seeding, though I did do better with predicting which teams didn’t make playoffs though.

  • Recall that to find the accuracy of the model using the MASE, subtract 100 from the (MASE * 100). Since the MASE rounds out to 10 as the nearest whole number (rounded to 2 decimal places), 100-10 gives us an accuracy of 90%

Last but not least, it’s prediction visualization time!

Before we go, the last thing I want to cover is how to visualize this year’s model’s predictions. Just like last year, we’re going to use the PYPLOT module from MATPLOTLIB:

import matplotlib.pyplot as plt

plt.scatter(y_test, yPredictions, color="red")
plt.xlabel('Actual values', size=15)
plt.ylabel('Predicted values', size=15)
plt.title('Actual vs Predicted values', size=15)
plt.show()

As you can see, the plot forms a sort of diagonal-line shape, which reinforces the model’s 90% prediction accuracy rate.

Also, just for comparison’s sake, here’s what my predictions looked like on last year’s model (the one where I used Select-K-Best to choose the model features):

This also looks like a diagonal-line shape, and last year’s model had a 91% accuracy rate.

Here’s the link to the Colab notebook in my GitHub-https://github.com/mfletcher2021/blogcode/blob/main/NBA_25_26_predictions.ipynb

Thanks for reading, and keep an eye out for my 2025-26 season predictions,

Michael

OCR Scenario 4: How Well Can Tesseract Read My Handwriting?

Advertisements

Hello everyone,

Michael here, and in today’s post, we’ll take a look at how well Tesseract could possibly read a sample of my handwriting.

So far, we’ve tested Tesseract against standard computer-font text, a photo of a banner with text, and a common US tax document. Aside from the standard computer-font text, Tesseract didn’t work well with either the banner or the tax document.

However, can Tesseract work well with reading my handwriting? Let’s find out!

But first, a little pre-processing…

Before we test Tesseract on my handwriting, let’s follow the pre-processing steps we’ve followed for the other three Tesseract scenarios: pip install the necessary packages and import them onto the IDE.

First, the pip installing:

!pip install pytesseract
!pip install opencv-python

Next, let’s import the necessary packages:

import pytesseract
import numpy as np
from PIL import Image

And now, the initial handwriting Tesseract test

Now, upon initial testing, how well can Tesseract read this sample of my handwriting?:

Let’s find out, shall we:

testImage = 'handwriting.png'
testImageNP = np.array(Image.open(testImage))
testImageTEXT = pytesseract.image_to_string(testImageNP)
print(testImageTEXT)

Output: 

Interestingly, Tesseract didn’t seem to pick up any text. I thought it might’ve picked up something, as the image simply contains black text on a white background. After all, there are no other objects in the image, nor is the information arranged like a document.

Could a little bit of image preprocessing be of any use with this image? Let’s find out!

Preprocessing time!

For this example, let’s try the same technique we used in the other two lessons-thresholding!

First off, let’s grayscale this image:

import cv2
from google.colab.patches import cv2_imshow

handwriting = cv2.imread('handwriting.png')
handwriting = cv2.cvtColor(handwriting, cv2.COLOR_BGR2GRAY)
cv2_imshow(handwriting)

Next, let’s do a little thresholding on the image. Since the image is black font with white text, let’s see how a different thresholding technique (THRESH_BINARY_INV) might be able to assist us here:

ret, thresh = cv2.threshold(handwriting, 127, 255, cv2.THRESH_BINARY_INV)
cv2_imshow(thresh)

The technique we used here-THRESH_BINARY_INV-is the opposite of what we used for the previous two lessons. In inverse binary thresholding, pixels above a certain threshold (127 in this case) turn black while pixels below this threshold turn white. I think this type of thresholding could be quite useful for handling black text on a white background, as was the case here.

Any luck reading?

Once we’ve done the thresholding, let’s see if that made a difference in the image’s Tesseract readability:

handwritingTEXT = pytesseract.image_to_string(thresh)
print(handwritingTEXT)

Output: 

Interestingly, unlike the previous two Tesseract scenarios we tested (the photo of the banner and the W-2 document), no text was read at all after thresholding.

Honestly, I thought the handwriting scenario would do far better than the banner photo or W-2 given that the contents of this image are simply black text on a white background. I mean, Tesseract was able to perfectly read the image in The Seven-Year Coding Wonder, and that was red text on a lime-green background. I guess this goes to show that while Tesseract has its potential, it also has several limitations as we’ve discovered.

Here’s the GitHub link to the Google Colab notebook for this post-https://github.com/mfletcher2021/blogcode/blob/main/OCR_handwriting_readings.ipynb.

Thanks for reading,

Michael

OCR Scenario 3: How Well Can Tesseract Read Documents?

Advertisements

Hello everybody,

Michael here, and in today’s post, we’ll be testing out another OCR/Tesseract scenario-how well can Tesseract read documents?

Here’s the document we’ll use for testing:

This is a standard-issue US W-2 form. For my international readers, a W-2 form is how US employees report their taxes to the federal government. All employee earnings and taxes withheld for a given calendar year are reported to the IRS (Internal Revenue Service, the agency that handles US taxpayer matters).

  • If you want to follow along with my Google Colab notebook, please save this image to your local drive and upload it to the IDE.

Let’s read the W-2

And now, let’s read in this W-2 form into our IDE. Before we start reading in the text to our IDE, let’s pip install the necessary packages if we don’t already have them:

!pip install pytesseract
!pip install opencv-python

Next, let’s import all the necessary packages to read in the image to the IDE:

import pytesseract
import numpy as np
from PIL import Image

Last but not least, let’s try to read in the W-2 form into the IDE and see what interesting results we’ll get:

testImage = 'w2 form.png'
testImageNP = np.array(Image.open(testImage))
testImageTEXT = pytesseract.image_to_string(testImageNP)
print(testImageTEXT)

| Deze | vow ] | * mens saat secur number For oficial Use Ory

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Li

5 Ebr otcaon abo eR a
© Eglo ra, area, DP Sle 7 Secale wane 7 Secarmeuny ew
Teaco wager mips | aca a
7 Secisecuy oe © mosses
& Conan 3 Deport cars oan
@ Employee's frst name and inal Ee E 11 Nonqualified plans {2a See instructions for box 12
3 ey a =e =
mi
14 Other We
a
f Employee's address and ZIP code u
{5 Sie Empayers mae Dane? Te Sia wagon he, a] 7 Sa icone ax ]16 Losalwagen tp 6] 10 Local oome ax] 20 lyme
I

 

 

 

_|

 

 

Department of the Treasury Internal Revenue Service
com Wr=2,_ Wage and Tax statement e025 Ta Pony fl uaa
Copy A—For Social Security Admi 1. Send this entire page with ‘Act Molios, see the seperate insimuctions.

Form W-8 to the Social Security Administration; photocopies are not acceptable.
Do Not Cut, Fold, or Staple Forms on This Page

Cat. No, 10134D

OK, so using Tesseract, it appears we have some improvement from the previous scenario detailed in OCR Scenario 2: How Well Can Tesseract Read Photos? in the sense that text was even picked up at all. It appears that some sections of the W-2 form were even read perfectly (such as the line that read Do Not Cut, Fold, or Staple Forms on This Page). However, the bulk of the results appear to be complete gibberish, with a surprising amount of misread words (insimuctions instead of instructions, for example).

Now that we know how well Tesseract reads documents, let’s work some preprocessing magic to see if it yields any improvements in the text-reading process.

W-2 preprocessing

Could thresholding actually improve the Tesseract reading’s accuracy like it did for the photo test (granted, that was a marginal improvement, but it was still something).

First, let’s grayscale the image:

import cv2
from google.colab.patches import cv2_imshow

w2 = cv2.imread('w2 form.png')
w2 = cv2.cvtColor(w2, cv2.COLOR_BGR2GRAY)
cv2_imshow(w2)

Now that the image has been gray-scaled, let’s try and threshold it using the same techniques we learned from the last post:

ret, thresh = cv2.threshold(w2, 127, 255, cv2.THRESH_BINARY)
cv2_imshow(thresh)

Now that we’ve run the thresholding process on the image, let’s see how well it read the text:

w2TEXT = pytesseract.image_to_string(thresh)
print(w2TEXT)

vag

 

jen -urtbee EINE

" ¢ kirpleyo"s -ane. adeross. a

 

 

D Errplayers social

 

code

uray sunt

For Official Use Only
‘OMB No. 1545-0029

4 sans,

3. Seca seounty wae

 

5 Mo

 

7 Seca seounty 198,

 

andtps

 

|

 

B Allocaree s198

 

 

  

 

 

 

 

 

 

 

 

 

4 Corte naib 8 10. Lope~dent sare oe-otts
Te Eirpleyors frat “ar 1 Wa See natruct ons to Bax Te
° 13 125
14 Oe We
ta
f Eirployos's adaross ave £ * ence
18 Se EB Deiter 2 sraaes. ips. ote 18 Loca sages tps cto] 19 Lea noone tax

 

 

   

 

 

 

 

 

 

 

com WE=-2 wage and Tax Statement

Copy A—For Social Security Administration. Sere ths entire page
te the Social Securty Admin stratio7: onotoccpies are not acceptan e

born Ww.

 

 

cOes

 

Do Not Cut, Fold, or Staple Forms on This Page

ane

 

ot the

 

 

‘easy inter
For Privacy Act and Paperwork Reduction
‘Act Notice. see the separate instructions.

No.

 

349)

Granted, the original Tesseract reading of the W-2 form wasn’t that great, but wow this is considerably worse! I mean, what kind of a phrase is Errplayers social? However, I’ll give Tesseract some credit for surprisingly reading phrases such as the For Privacy Act and Paperwork Reduction correctly. Then again, I noticed the phrases in the document that Tesseract read the most accurately were the phrases in bold typeface.

Another one of Tesseract’s limitations?

Just as we saw when we tested Tesseract on the photo of the banner, we see that Tesseract has its limitations on reading documents as well. Interestingly enough, when we ran preprocessing on the photo of the banner, the preprocessing helped extract some text from the photo of the banner. However, when we ran the same preprocessing on the photo of the W-2, the reading came out worse than the reading we got from the original, un-processed image.

Why might that be? As you can see from the thresholding we did on the image of the W-2, most of the text in the form itself (namely the sections that contain people’s taxpayer information) comes out like it had been printed on a printer that was overdue for a black ink cartridge replacement. Thus, Tesseract wouldn’t have been able to properly read the text that came out of the image with the thresholding.

Then again, when Tesseract tried to read the text on the original, un-processed image, the results weren’t that great either. This could be because W-2 forms, like many legal forms, have a complex, multi-row layout that isn’t suited for Tesseract’s reading capabilities.

  • Personally, one reason I thought Tesseract would read this document better than the photo from the previous post is that the document’s text is not in a weird font and there’s nothing in the background of the document. I guess the results go to show that even with the things I just mentioned about the document, Tesseract still has its limitations.

Here’s the GitHub containing my Google Colab notebook for this post-https://github.com/mfletcher2021/blogcode/blob/main/OCR_document_readings.ipynb.

Thanks for reading,

Michael

OCR Scenario 2: How Well Can Tesseract Read Photos?

Advertisements

Hello everyone,

Michael here, and in today’s post, we’ll see how well OCR and PyTesseract can read text from photos!

Here’s the photo we will be reading from:

This is a photo of a banner at Nashville Farmer’s Market, taken by me on August 29, 2025. I figured this would be a good example to testing how well OCR can read text from photos, as this banner contains elements in different colors, fonts, text sizes, and text alignments (I know you might not be able to notice at first glance, but the Nashville in the Nashville Farmers Market logo on the bottom right-hand corner of this banner is on a small yellow background).

Let’s begin!

But first, the setup!

Before we dive right in to text extraction, let’s read the image to the IDE and install & import any necessary packages. First, if you don’t already have these modules installed, run the following commands on either your IDE or CLI:

!pip install pytesseract
!pip install opencv-python

Next, let’s import the following modules:

import pytesseract
import numpy as np
from PIL import Image

And now, let’s read the image!

Now that we’ve got all the necessary modules installed and imported, let’s read the image into the IDE:

testImage = 'farmers market sign.jpg'
testImageNP = np.array(Image.open(testImage))
testImageTEXT = pytesseract.image_to_string(testImageNP)
print(testImageTEXT)

Output: [no text read from image]

Unlike the 7 years image I used in the previous lesson, no text was picked up by PyTesseract from this image. Why could that be? I have a few theories as to why no text was read in this case:

  • There’s a lot going on in the background of the image (cars, pavilions, etc.)
  • PyTesseract might not be able to understand the fonts of any of the elements on the banner as they are not standard computer fonts
  • Some of the elements on the banner-specifically the Nashville Farmers’ Market logo on the bottom right hand corner of the banner don’t have horizontally-aligned text and/or the text is too small for PyTesseract to read.

Can we solve this issue? Let’s explore one possible method-image thresholding.

A little bit about thresholding

First of all, I figured we can try image thresholding to read the image text for two reasons: it might help PyTesseract read at least some of the banner text AND it’s a new concept I haven’t yet covered in this blog, so I figured I could teach you all something new in the process.

Now, as for image thresholding, it’s the process where grayscale images are converted to a two-colored image using a specific pixel threshold (more on that later). The two colors used in the new thresholding image are usually black and white; this helps emphasize the contrast between different elements in the image.

And now, let’s try some thresholding!

Now that we know a little bit about what image thresholding is, let’s try it on the banner image to see if we can extract at least some text from it.

First, let’s read the image into the IDE using cv2.read() and convert it to grayscale (thresholding only works with gray-scaled images):

import cv2
from google.colab.patches import cv2_imshow

banner = cv2.imread('farmers market sign.jpg')
banner = cv2.cvtColor(banner, cv2.COLOR_BGR2GRAY)
cv2_imshow(banner)

As you can see, we now have a grayscale image of the banner that can be processed for thresholding.

The thresholding of the image

Here’s how we threshold the image using a type of thresholding called binary thresholding:

ret, thresh = cv2.threshold(banner, 127, 255, cv2.THRESH_BINARY)
cv2_imshow(thresh)

The cv2.threshold() method takes four parameters-the grayscale image, the pixel threshold to apply to the image, the pixel value to use for conversion for pixels above and below the threshold, and the thresholding method to use-in this case, I’m using cv2.THRESH_BINARY.

Now, what is the significance of the numbers 127 and 255? 127 is the threshold value, which means that any pixel with an intensity less than or equal to this threshold will be set to black (intensity 0) while any pixel with an intensity above this value will be set to white (intensity 255). While 127 isn’t a required threshold value, it’s ideal because it’s like a midway point between the lowest and highest pixel intensity values (0 and 255, respectively). In other words, 127 is a quite useful threshold value for helping to establish black-and-white contrast in image thresholding. 255, on the other hand, represents the pixel intensity value to use for any pixels above the 127 intensity threshold. As I mentioned earlier, white pixels have an intensity of 255, so any pixels in the image above a 127 intensity are converted to a 255 intensity, so those pixels turns white while pixels at or below the threshold are converted to a 0 intensity (black).

  • A little bit about the ret parameter in the code: this value represent the pixel intensity threshold value you want to use for the image. Since we’re doing simple thresholding, ret can be used interchangeably with the thresholding value we specified here (127). For more advanced thresholding methods, ret will contain the calculated optimal threshold.

And now the big question…will Tesseract read any text with the new image?

Now that we’ve worked OpenCV’s thresholding magic onto the image, let’s see if PyTesseract picks up any text from the image:

bannerTEXT = pytesseract.image_to_string(thresh)
print(bannerTEXT)

a>
FU aba tee
RKET

Using the PyTesseract image_to_string() method on the new image, the only real improvement here is that there was even text read at all. It appears that even after thresholding the image, PyTesseract’s output didn’t even pick up anything close to what was on the banner (although it surprisingly did pick up the RKET from the logo on the banner).

All in all, this goes to show that even with some good image preprocessing methods, PyTesseract still has its limits. I still have several other scenarios that I will test with PyTesseract, so stay tuned for more!

Here’s the GitHub link to the Colab notebook used for this tutorial (you will need to upload the images again to the IDE, which can easily be done by copying the images from this post, saving them to your local drive, and re-uploading them to the notebook)-https://github.com/mfletcher2021/blogcode/blob/main/OCR_photo_text_extraction.ipynb.

Thanks for reading,

Michael

How To Use OCR Bounding Boxes

Advertisements

Hello everyone,

Michael here, and today’s post will be a lesson on how to use bounding boxes in OCR.

You’ll recall that in my 7th anniversary post The Seven-Year Coding Wonder I did an introduction to Python OCR with the Tesseract package. Now, I’ll show you how to make bounding boxes, which you can use in your OCR analyses.

But first, what are bounding boxes?

That’s a very good question. Simply put, it’s a rectangular region that denotes the location of a specific object-be it text or something else-within a given space.

For instance, let’s take this restaurant sign. The rectangle I drew on the COME ON IN part of the sign would serve as a bounding box

In this case, the red rectangular bounding box would denote the location of the COME ON IN text.

You can use bounding boxes to find anything in an image, like other text, other icons on the sign, and even the shadow the sign casts on the sidewalk.

Bounding boxes, tesseract style!

Now that we’ve explained what bounding boxes are, it’s time to test them out on an image with Tesseract!

Here’s the image we’ll test our bounding boxes on:

Now, how do we get our bounding boxes? Here’s how:

  • Keep in mind, I will continue from where I left off on my 7-year anniversary post, so if you want to know how to read the image and print the text to the IDE, here’s the post you should read-The Seven-Year Coding Wonder.

First, install the OpenCV package:

!pip install opencv-python

Next, run pytesseract’s image_to_data() method on the image and print out the resulting dictionary:

sevenYears = pytesseract.image_to_data(testImageNP, output_type=pytesseract.Output.DICT)
print(sevenYears)

{'level': [1, 2, 3, 4, 5, 5, 5, 5, 4, 5, 5], 'page_num': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'block_num': [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'par_num': [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'line_num': [0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2], 'word_num': [0, 0, 0, 0, 1, 2, 3, 4, 0, 1, 2], 'left': [0, 528, 528, 571, 571, 1069, 1371, 1618, 528, 528, 1297], 'top': [0, 502, 502, 502, 504, 529, 502, 504, 690, 690, 692], 'width': [2129, 1205, 1205, 1124, 452, 248, 200, 77, 1205, 714, 436], 'height': [1399, 313, 313, 125, 97, 98, 99, 95, 125, 99, 123], 'conf': [-1, -1, -1, -1, 96, 96, 96, 96, -1, 96, 96], 'text': ['', '', '', '', 'Thank', 'you', 'for', '7', '', 'wonderful', 'years!']}

Now, what does all of this juicy data mean? Let’s dissect it key-by-key:

  • level-The element level in Tesseract output (1 indicates page, 2 indicates block, 3 indicates paragraph, 4 indicates line and 5 indicates word)
  • page_num-The page number on the document where the object was found; granted, this is just a one-page image we’re working with, so this information isn’t terribly useful (though if we were working with a PDF or multi-page document, this would be very helpful information)
  • block_num-This indicates which chunk of connected text (paragraph, column, etc.) an element belongs to (this runs on a 0-index system, so 0 indicates the first chunk)
  • par_num-The paragraph number that a block element belongs to (also runs on a 0-index system)
  • line_num-The line number within a paragraph (also runs on a 0-index system)
  • word_num-The word number within a line (also runs on a 0-index system)
  • left & top-The X-coordinate for the left boundary and Y-coordinate for the top boundary of the bounding box, respectively
  • width & height-The width & height in pixels, respectively, of the bounding box
  • conf-The OCR confidence value (from 0-100, 100 being an exact match) that the correct word was detected in the bounding box. If you see a conf of -1, the element has no confidence value as its not a word
  • text-The actual text in the bounding box

Wow, that’s a lot of information to dissect! Another thing to note about the above output-not all of it is relevant. Let’s clean up the output to only display information related to the words in the image:

import pandas as pd

sevenYearsDataFrame = pd.DataFrame(sevenYears)
sevenYearsWords = sevenYearsDataFrame[sevenYearsDataFrame['level'] == 5]
print(sevenYearsWords)

    level  page_num  block_num  par_num  line_num  word_num  left  top  width  \
4       5         1          1        1         1         1   571  504    452   
5       5         1          1        1         1         2  1069  529    248   
6       5         1          1        1         1         3  1371  502    200   
7       5         1          1        1         1         4  1618  504     77   
9       5         1          1        1         2         1   528  690    714   
10      5         1          1        1         2         2  1297  692    436   

    height  conf       text  
4       97    96      Thank  
5       98    96        you  
6       99    96        for  
7       95    96          7  
9       99    96  wonderful  
10     123    96     years!  

Granted, it’s not necessary to convert the image dictionary into a dataframe, but I chose to do so since dataframes are quite versatile and easy to filter. As you can see here, we have all the same metrics we got before, just for the words (which is what we really wanted).

And now, let’s see some bounding boxes!

Now that we know how to find all the information about an image’s bounding boxes, let’s figure out how to display them on the image. Granted, the pytesseract library won’t actually draw the boxes onto the images. However, we can use another familiar library to help us out here-OpenCV (which I did a series on in late 2023).

First, let’s install the opencv-python module onto our IDE if it’s not already there:

!pip install opencv-python
  • Remember, no need for the exclamation point at the front of the string if your running this command on a CLI.

Next, let’s read the image into the IDE:

import cv2
from google.colab.patches import cv2_imshow

sevenYearsTestImage = cv2.imread('7 years.png', cv2.IMREAD_COLOR)
cv2_imshow(sevenYearsTestImage)
cv2.waitKey(0)

After installing the opencv module in the IDE, we then read the image into the IDE using the cv2.imread() method. The cv2.IMREAD_COLOR ensures we read and display this image in its standard color format.

  • You may be wondering why we’re reading the image into the IDE again, especially after reading it in with pytersseract. We need to read the image again as pytesseract will only read the image string into the IDE, not the image itself. We need to read in the actual image in order to display the bounding boxes.
  • If you’re not using Google Colab as your IDE, no need to include this line-from google.colab.patches import cv2_imshow. The reason Google Colab makes you include this line is because the cv2.imshow() method caused Google Colab to crash, so think of this line as Google Colab’s fix to the problem. It’s annoying I know, but it’s just one of those IDE quirks.

Drawing the bounding boxes

Now that we’ve read the image into the IDE, it’s time for the best part-drawing the bounding boxes onto the image. Here’s how we can do that:

sevenYearsWords = sevenYearsWords.reset_index(drop=True)

howManyBoxes = len(sevenYearsWords['text'])

for h in range(howManyBoxes):
  (x, y, w, h) = (sevenYearsWords['left'][h], sevenYearsWords['top'][h], sevenYearsWords['width'][h], sevenYearsWords['height'][h])
  sevenYearsTestImage = cv2.rectangle(sevenYearsTestImage, (x, y), (x + w, y + h), (255, 0, 0), 3)

cv2_imshow(sevenYearsTestImage)

As you can see, we can now see our perfectly blue bounding boxes on each text element in this image. The process also worked like a charm, as each text element is captured perfectly inside each bounding box-then again, it helped that each text element had a 96 OCR confidence score (which ensured high detection accuracy).

How did we get these perfectly blue bounding boxes?

  • I first reset the index on the sevenYearsWords dataframe because when I first ran this code, I got an indexing error. Since the sevenYearsWords dataframe is essentially a subset of the larger sevenYearsDataFrame (the one with all elements, not just words), the indexing for the sevenYearsWords dataframe would be based off of the original dataframe, so I needed to use the reset_index() command to reset the indexes of the sevenYearsWords dataframe to start at 0.
  • Keep this method (reset_index()) in mind whenever you’re working with dataframes generated as subsets of larger dataframes.
  • howManyBoxes would let the IDE know how many bounding boxes need to be drawn-normally, you’d need as many bounding boxes as you have text elements
  • The loop is essentially iterating through the elements and drawing a bounding box on each one using the cv2.rectangle() method. The parameters for this method are: the image where you want to draw the bounding boxes, the x & y coordinates of each box, the x-coordinate plus width and y-coordinate plus height for each box, the BGR color tuple of the boxes, and the thickness of the boxes in pixels (I went with 3-px thick blue boxes).

Come find the code on my GitHub-https://github.com/mfletcher2021/blogcode/blob/main/OCR_bounding_boxes.ipynb.

Thanks for reading!

Michael

2024-25 NBA Season Predictions Revisited & OKC’s First Championship

Advertisements

Hello readers,

As some of you may recall, I did a pair of NBA 2024-25 series prediction posts around the start of the 2024-25 season back in October (Python, Linear Regression & the 2024-25 NBA season and Python, Linear Regression & An NBA Season Opening Day Special Post).

Well, now that the season has just concluded, I thought it would be a good time to revisit those predictions and reflect on the season that just passed.

Yes, I know this is primarily a coding blog, but all the storylines of this past NBA season (the Finals, the midseason trades, etc.) are certainly worth revisiting. After all, this post will tie into my NBA season opener two-parter (juicy Python AI predictions and all).

But first, a few words on the NBA finals…

Wow wow wow what a Finals that was! I mean, first of all, there were two solid teams in the Indiana Pacers (50-32, 4th in East) and Oklahoma City Thunder (68-14, 1st in West). Second of all, it went the distance as it was the first NBA Finals to go a full 7 games since the 2016 Finals (that of the Warriors’ blown 3-1 lead and the Lebron-led Cavs’ comeback-like-no-other).

The momentum was back-and-forth, and it felt like the series could’ve gone in favor of either the Pacers or Thunder. Tyrese Haliburton’s Game 1 2-point clutch shot to give the Pacers the Game 1 win. OKC rallying back with their defense in Game 2 to keep the Pacers at bay. The Pacers’ blowout Game 6 win at home to even force a Game 7. OKC rallying back in the second half of Game 7, holding the Pacers to just 42 second-half points to ultimately win their first championship (and congrats to Shai Gilgeous-Alexander on winning 2025 NBA Finals & 2025 regular season MVP). I’ve got to hand it to the Pacers’ resilience in Game 7, because even though they didn’t win, they-especially their bench players like TJ McConnell-gave it their all after Tyrese Haliburton went out in the first quarter with a torn Achilles (well wishes to him on recovery).

The mid-season trades (especially THAT trade)

It wouldn’t be an NBA season-in-review/predictions-revisited post without reviewing all of the crazy stuff that went down in the midseason trades.

Personally, as wild as off-season free agency can be (anyone remember LeBron’s “I’m taking my talents to South Beach”?) I think the mid-season trades can be even wilder, especially this season. Let’s go over the most notable examples, shall we?

  • Jimmy Butler (Miami Heat –> Golden State Warriors)
  • De’Aaron Fox (Sacramento Kings –> San Antonio Spurs)
  • Khris Middleton (Milwaukee Bucks –> Washington Wizards)
  • Andrew Wiggins (Golden State Warriors –> Miami Heat-part of the Jimmy Butler trade package)

And for the most notable 2025 midseason trade (yes THAT trade):

  • Anthony Davis from the LA Lakers to the Dallas Mavericks/Luka Doncic from the Dallas Mavericks to the LA Lakers

Personally, I thought this takes the cake for most shocking midseason NBA trade in quite some time! I mean, no one expected (least of all Doncic himself) that Dallas would trade away a franchise icon in his prime-he’s 26-for a 32-year-old Anthony Davis. It’s especially wild considering A) it was largely done in secrecy and B) Luka took the Mavs to the Finals just the previous year. Time will tell how this plays out, but personally, I think the Lakers got the better deal (getting Markeiff Morris and Maxi Klieber in that package didn’t hurt either). After all, the Lakers got the 3-seed in the West this year, while the Mavericks fell to the 10-seed and didn’t make it past play-in (though the Mavs’ especially bad luck with injuries didn’t help). Luka could certainly bring the Lakers much success, especially in the post-LeBron years to come.

  • Side note: it will be interesting to see how Dallas’s #1 overall 2025 draft pick of Cooper Flagg will pan out. Perhaps he’ll be the new Doncic in Dallas and fit right in with Klay, Kyrie and the rest of the Mavs?

And now, we reflect…

So, how accurate were my opening day predications for the season? Let’s review those predictions shall we?

Eastern Conference Edition

So, here were my opening day predictions for how things in the Eastern Conference would pan out:

INTO THE PLAYOFFSINTO THE PLAY-INOUT OF PLAYOFF RUNNING
1. Milwaukee Bucks7. Cleveland Cavaliers11. Chicago Bulls
2. Boston Celtics8. Indiana Pacers12. Washington Wizards
3. Philadelphia 76ers9. Atlanta Hawks13. Charlotte Hornets
4. Miami Heat10. New York Knicks14. Orlando Magic
5. Toronto Raptors15. Detroit Pistons
6. Brooklyn Nets

Here’s how things actually played out:

INTO THE PLAYOFFSINTO THE PLAY-INOUT OF PLAYOFF RUNNING
1. Cleveland Cavaliers 7. Orlando Magic (made playoffs)11. Toronto Raptors
2. Boston Celtics8. Atlanta Hawks12. Brooklyn Nets
3. New York Knicks9. Chicago Bulls13. Philadelphia 76ers
4. Indiana Pacers10. Miami Heat (made playoffs)14. Charlotte Hornets
5. Milwaukee Bucks15. Washington Wizards
6. Detroit Pistons

In total, here’s a rundown of how right/off I was:

  • Only 2/6 teams I picked to get a top-6 spot in the East (and get a direct playoff spot) were correct (Bucks and Celtics)
  • 3/6 teams I picked to get a top-6 spot in the East didn’t even make play-in (Nets, Sixers, and Raptors)
  • Only 1 correct play-in pick for the East (Hawks)-the other three play-in teams actually got top-6 spots
  • 3/5 teams that I thought weren’t going to make playoffs at least made play-in (Magic, Pistons, and Bulls). None of these teams got further than the Eastern Conference quarterfinals, however.
  • Of the 15 teams in the East, 7 teams finished better than I expected, 7 teams finished worse than I expected, and 1 team finished in the exact spot I thought they would.
  • Teams that finished better than I thought they would: Cavaliers, Knicks, Pacers, Pistons, Hawks, Bulls, and Magic
  • Teams that finished worse than I thought they would: Bucks, Heat, Nets, 76ers, Raptors, Wizards, Hornets
  • Team that finished exactly as I thought they would: Celtics (2-seed)

Takeaways from the East…

  • The Miami Heat, despite finishing 10th in the East, earning a play-in spot for the third straight season, and all the Jimmy Butler drama leading up to the midseason trade, still managed to surprise me in that they became the first 10-seed playoff team to make the playoffs. Then they promptly got swept by the Cleveland Cavaliers in the first round, including two straight 30+ point losses at home. Maybe the lottery pick might’ve been better for them?
  • I know I’ve talked about earlier in this post, but I’ve got to give the Indiana Pacers credit where credit is due. Despite a slow 10-15 start to the season, they eventually found their rhythm, both offensively and defensively. The return of key players like Aaron Neismith and Tyrese Haliburton’s 2nd-half-of-the-season resurgence certainly helped propel the Pacers to the NBA Finals (and help the series go the distance against the OKC Thunder)
  • On the other side of the coin, teams like the Philadelphia 76ers fell far below expectations this season, finishing with a 13-seed in the East, no playoffs or play-in, and a 24-58 record. The highly-anticipated acquisition of Paul George from the LA Clippers and the formation of a new Sixers’ Big-3 with George, Joel Embiid and Tyrese Maxsey didn’t quite pan out, especially due to a knee injury for George, a torn meniscus for Embiid, and a sprained finger for Maxsey, which ended up becoming season-ending injuries for all three players.
  • The Milwaukee Bucks, despite finishing with a 5-seed in the East and a solid regular season, certainly didn’t look like the championship Bucks squad of 2020-21. Injuries to Giannis Antetokounmpo and Damian Lilliard didn’t help matters in the playoffs, nor did their blown late-game playoff leads-especially their infamous 118-111 lead in OT of game 5 against the Pacers…that the Pacers still won by 1, thus eliminating the Bucks.

Western Conference Edition

Now let’s see how things panned out in the Western Conference? Here’s how I thought things would go:

INTO THE PLAYOFFSINTO THE PLAY-INOUT OF PLAYOFF RUNNING
1. Denver Nuggets7. LA Lakers11. Sacramento Kings
2. LA Clippers8. Memphis Grizzlies12. New Orleans Pelicans
3. Golden State Warriors9. Oklahoma City Thunder13. San Antonio Spurs
4. Phoenix Suns10. Minnesota Timberwolves14. Portland Trailblazers
5. Dallas Mavericks15. Houston Rockets
6. Utah Jazz

And here’s how things actually went for the teams in the West:

INTO THE PLAYOFFSINTO THE PLAY-INOUT OF PLAYOFF RUNNING
1. Oklahoma City Thunder7. Golden State Warriors (made playoffs)11. Phoenix Suns
2. Houston Rockets8. Memphis Grizzlies (made playoffs)12. Portland Trail Blazers
3. LA Lakers9. Sacramento Kings13. San Antonio Spurs
4. Denver Nuggets10. Dallas Mavericks14. New Orleans Pelicans
5. LA Clippers15. Utah Jazz
6. Minnesota Timberwolves

In total, here’s a rundown of how right/off I was (Western Conference edition).

  • It’s worth noting that my Western Conference predictions have several similarities with my Eastern Conference predictions:
    • 2/6 teams that I predicted would get a top-6 spot did just that (Clippers and Nuggets)
    • 3/6 teams that I predicted would get a top-6 spot didn’t even make the play-in (Suns, Mavericks and Jazz)
    • Only 1 correct play-in pick for the west (Grizzlies)-the other three teams were straight shots into the playoffs (with “9-seed” OKC finishing as the champions)
    • For both the East and West, the teams I had finishing as the “bottom seed” both made it directly to playoffs (with Pistons as 6-seed and Rockets as 2-seed)
    • There were some differences between my East predictions and West predictions:
    • Only 2/6 teams that I thought weren’t going to playoffs at least made play-ins (Kings and Rockets). Just like the East though, neither of these teams made it past the Western Conference quarterfinals.
    • In total, 6 teams in the West finished better than I expected, 7 teams finished worse than I expected, and 2 teams finished in the exact spot I expected.
    • Teams that finished better than I thought they would: Lakers, Thunder, Timberwolves, Kings, Trailblazers, Rockets
    • Teams that finished worse than I thought they would: Nuggets, Clippers, Warriors, Mavericks, Jazz, Suns, Pelicans
    • Teams that finished in the exact spot I thought they would: Spurs (13-seed) and Grizzlies (8-seed)

Takeaways from the west…

  • One team I certainly underestimated was the Houston Rockets. To think my Michael-made ML model put them as the 15-seed in the West-boy did they outperform my model’s expectations (finishing as the 2-seed in the West). Sure, they did get upset in the Western Conference first round by the play-in Warriors, but the combination of good veterans with Fred VanVleet and Jeff Green, solid offensive contributors like Alpern Sengun, solid defensive contributors with guys like Amen Thompson, and coach Ime Udoka, I think the Rockets will go far in the years to come. Now to see how adding KD in free agency will elevate the Rockets.
  • And now for the Warriors…I mean, at least they’ve got a solid Big-3 in Draymond Green, Jimmy Butler and of course, Stephen Curry. However, they did have an up-and-down season, finishing with a 48-34 record and a 7-seed play-in spot. The mid-season acquisition of Jimmy Butler from the Heat certainly gave the Warriors more defensive spark (and in my opinion, I think Miami got a solid asset in Andrew Wiggins). I think it would do the Warriors a lot of good to think about their future-after all, they can only roll with guys like Jimmy Butler and Draymond Green for so long-both of whom will turn 36 next season. Plus I think it would be satisfying to make one more championship run with Steph Curry and Draymond Green, who have been with the Warriors for 15+ years and four championships.
  • On the other end of the playoff coin, what happened to the bottom-of-the-West (and bottom-of-the-NBA) Utah Jazz? Granted, I know they haven’t been great since the departures of Rudy Gobert and Donovan Mitchell, but losing one of their most promising assets in Taylor Hendricks to a fractured fibula and dislocated ankle just three games into the season certainly didn’t help matters for the Jazz. Hopefully the Jazz’ 2025 draft class-led by small forward Ace Bailey-will reverse their fortunes and bring them back into playoff contention. Maybe adding Kevin Love in free agency will give them a boost of veteran player-coach leadership?
  • Last but not least, let’s talk about the San Antonio Spurs-one of only three teams whose seeding I got exactly right (13-seed). Granted, going from 14-seed last season to 13-seed this season might not sound like a huge improvement, but I think Victor Wembanyama definitely has potential to be a franchise icon, as he showed his defensive potential this season despite his season-ending blood clot after All-Star Break. The mid-season acquisition of De’Aaron Fox and the coaching transition from Coach “Pop” Popovich to Mitch Johnson could help rejuvenate the Spurs and, who knows, maybe even bring them back to the playoffs for the first time since 2019.

Thanks for reading. What a great NBA season it was! I’ll certainly admit I had fun writing this 3-post series to not only try and predict the upcoming NBA season, but also to reflect on how my predictions fared after the season has concluded. After all, an important skill of data analysis is learning how to gather insights from the data!

Michael

Also, congrats OKC on winning the 2025 NBA championship!

The Seven-Year Coding Wonder

Advertisements

Hello everyone,

Michael here, and today we’ve got another very special post! Yes dear readers, it’s time for another anniversary post-this year, Michael’s Programming Bytes turns 7!

So, how will I celebrate this anniversary? With a generic thank-you statement?

Not quite. I’ll take a cue from my 6th anniversary post (where I gave a surprise Python encryption lesson-https://michaelsprogrammingbytes.com/6-2/) and instead, show you something cool in Python to celebrate!

Let’s begin!

A little something called OCR

For this year’s anniversary celebration post, I’m going to a demo on something called OCR using Python.

What is OCR? It’s optical character recognition, which is a type of computer vision-which is a type of AI-that essentially reads any text in images and outputs the text it read to the program (to the best of its ability of course, as OCR like other types of AI can certainly make mistakes).

And now, for the OCR demo

What image might we be reading into the IDE?

Yes, we’ve got some red text and a lime-green image background (if you’ve read enough of this blog, you’ll notice that I’m quite fond of the lime-green-background-and-red-text image). Now, let’s see how well our OCR demo fares!

Package…installed

Before we start our fun little demo, let’s first install any necessary packages:

!pip install pytesseract

For this lesson, the only package we’ll need to install is the pytesseract package, which is a popular Python OCR library.

Imports…made

Next up, let’s import any modules we’ll need for the demo:

import pytesseract
import numpy as np
from PIL import Image

We’ll need three modules-pytesseract, numpy and PIL. As I just mentioned, pytesseract is the OCR library that we’ll use for this post. The PIL (Python Imaging Library/Pillow) library is like a companion to pytesseract, since even though PIL doesn’t do the actual OCR, it helps with all the important image preprocessing tasks (e.g. opening image into IDE, converting image to greyscale for optimal OCR). We’ve already explored the numpy library a while back, but I’ll explain why we’re using here shortly.

Image text…read

Last but not least, let’s read the image’s text:

testImage = '7 years.png'
testImageNP = np.array(Image.open(testImage))
testImageTEXT = pytesseract.image_to_string(testImageNP)
print(testImageTEXT)

Thank you for 7
wonderful years!

After storing the image with text as the testImage variable, we then use both the PIL Image module and the numpy module to open and preprocess the image.

  • If you’re using a Google Colab notebook for this tutorial like I did, remember to save any files (images, CSVs) to the Colab runtime, as Colab can’t easily access your local drives.

Why do we need both modules? The Image module opens Why do we need both modules? The Image module reads the testImage into the IDE while numpy helps preprocess the image further by translating it to a series of pixels, which assists with further preprocessing tasks (such as greyscaling and text extraction).

After opening and preprocessing the image, we then use the pytesseract.image_to_string()-passing in the numpy version of the test image (testImageNP in this case)-method to save the extracted text as an object. Last but not least, we then print out the extracted text and see how accurate it is. In this case, we got Thank you for 7 \n wonderful years, which is exactly what the text on the image says (it even got the \n line break right!).

All in all, this simple OCR program seems to work like a charm! I might even do a whole Python OCR series where I test Python’s OCR capabilities for a bunch of interesting scenarios-but you’ll have to keep following & reading my blog to enjoy my cool coding content.

Since I used a Google Colab notebook to put this anniversary post together, here’s the link to that notebook in my GitHub-https://github.com/mfletcher2021/blogcode/blob/main/7.ipynb. The test image is also on my GitHub, but it is attached separately (the image is called 7 years.png).

One more thing…

…I know I say this after every post, but thank you for following along my coding how-to journey for the last 7 years and 187 posts (pushing the 200-mark)! Hopefully you’ve picked up some new skills, or at least some juicy knowledge to impress on bar trivia night (I mean, I wouldn’t be disappointed in that), about the wonderful world of coding-or any other thing I’ve happened to reference on this blog over the last 7 years.

Also, if you’re wondering how long I’ll keep going, have no fear, I’ve got content for years!

Keep calm and code on!

Michael

Also, let me leave you with a picture of these two furry kittes-Simba and Marbles on Christmas Day 2017-they certainly saw me write some of my earliest content:

Fun with HTML

Advertisements

Hello everybody,

Michael here, and in this lesson, I thought I’d run it back to a topic I haven’t covered much lately-web development and specifically HTML.

Now, in the way, way past of 2021-2022 I covered some basic HTML concepts like table design and list creation. However, now that I’ve introduced you to the basics, I thought we can have some fun with HTML these next few posts!

<html>Basic outline of website</html>

Before we try out any of the fun new features of HTML, let’s first design the basic website:

<html>
    <body>
        <h1>Michael Plays Around With HTML</h1>
        <h2>In this website, Michael will play around with some cool HTML Easter eggs!</h2>   
    </body>
</html>

Simple enough website outline, right? Here’s what it looks like (on Chrome):

<body><h1>One thing I should note</h1></body>

Before we explore some cool HTML Easter eggs, I think this is worth mentioning. Back in my original series of HTML/CSS content, I was using Atom Text Editor to help me create my content. However, Atom Text Editor reached end-of-life in December 2022. This means that while you can technically still use Atom for your HTML/CSS needs, you would need to work with an older version as no new updates/patches will be released for Atom.

With that said, from here on out, I’ll be using Microsoft Visual Studio Code for any HTML/CSS lessons (it’s not the same thing as Microsoft Visual Studio that I used for my recent C# content). Here’s the link to install it-https://code.visualstudio.com/-like Microsoft Visual Studio, this software is open-source.

Also, once you’ve installed Microsoft Visual Studio on your device and have your code set up, here’s how to run the code:

On the ribbon on the top of the screen, click the Run button and you can either select Start Debugging (F5 shortcut key for Windows devices) or Run Without Debugging (CTRL + F5 shortcut key for Windows devices).

<p>The juicy HTML Easter eggs</p>

Now that we’ve got a very basic website, let’s have some fun with HTML easter eggs, shall we?

First off, let’s add an HTML color picker to our webpage:

<label for="colorpickertest">First, pick a HEX color, any color:</label>
<input type="color" id="colorpickertest" value="#ff0000">

As you can see, below the header that begins In this website..., I added a color-picker along with the label First, pick a HEX color, any color:.

One thing to note about the color-picker (denoted in the <input> tag)-you can use any color as the default, but it must be in HEX format. I used my favorite color red, which is represented in HEX code as #FF0000. If you don’t specify a default color that you’d like to use, black (HEX code #000000) will be the default color.

  • Even though the default color you want to use for the color-picker must be in HEX format, the color picker itself gives you the option of viewing colors in HEX, RGB or HSV formats.
  • For a refresher on color systems in programming, please check out my post Colors in Programming.

<datalist>Autocomplete

The next HTML Easter egg we’ll explore is the use of auto-complete:

<p>Which Marvel character is your favorite?</p>
<input list="heroes">
<datalist id="heroes">
    <option>M'Baku</option>
    <option>Mordo</option>
    <option>Maximoff</option>
    <option>Magneto</option>
    <option>Malekith</option>
    <option>Mantis</option>
 </datalist>

In this example, I have a seemingly-ordinary HTML dropdown with the header Which Marvel character is your favorite? followed by the names of six random Marvel characters.

However, when I type Ma into the search bar, I see the dropdown auto-populate with the names of the four Marvel characters in the list that start with Ma (sadly I couldn’t grab a screenshot of this).

  • As impressive as it might be, unfortunately I cannot program the autocomplete to gather your whole search history. I don’t have the AI capabilities of Google, but wouldn’t that be something?

<p>Feel free to write anything</p>

Now if you have a basic grasp of HTML, you’ve probably seen the </p> tag. However, let me show you something cool you can do with the <p> tag:

<p contenteditable="true">Feel free to edit this content.</p>

This is just like the regular <p> tag in the sense that you can create regular HTML paragraphs, but with one notable exception in the form of contenteditable. Adding this parameter to the <p> tag and setting its value to true will create a freeform text space, where you can edit the paragraph as much as you’d like.

  • Honestly, I think it’s a good idea to include the placeholder text (Feel free to edit this content in this example) as a default.

Now, here’s the paragraph with the default text:

And here’s the paragraph after I edit the text:

Pretty neat, eh?

<progress> & <meter>

For my last pair of HTML Easter eggs, let’s explore the <progress> bar and <meter> bar.

First, we explore the progress bar:

<label for="testbar">Percent of 2025 complete:</label>
<progress id="testbar" value="39" max="100"> 39% </progress>

And here’s the progress bar:

As you can see, we have a simple blue progress bar that appears to be 39% filled. How did I make the progress bar look like its shown on the webpage? In the <progress> tag, I set the value of the <value=> parameter to 39 and the value of the <max=> parameter to 100.

Next, let’s check out the <meter> bar:

<label for="testmeter">Current phone charge:</label>
<meter id="testmeter" min="0" low="0.15" high="0.85" max="1" value="0.92">92%</meter>

In this example, I created a <meter> with five parameters-the id (linking the meter to the above <label>), the min (lowest possible value for meter), the low (along with min, represents lower end of meter range), the high (represents higher end of meter range), and the max (highest possible value for the meter).

The <meter> tag acts similarly to the <progress> tag in the sense that both, in a way, represent percentages of something (whether phone charge, grade, you name it). However, one thing that sets the <meter> tag apart is that it can change its color based on its current value.

Let’s demonstrate the color-changing ability of the <meter> on the same code snippet:

<label for="testmeter">Current phone charge:</label>
<meter id="testmeter" min="0" low="0.15" high="0.85" max="1" value="0.66">66%</meter>

Since I changed the meter’s value to 0.66, the color changed to green. This effect will only work if you have a low and high value defined, as the meter’s green display will only work if the current value is between a defined low and high value.

  • Just to note-using min="0" low="0.15" high="0.85" max="1" value="0.66" or min="0" low="15" high="85" max="100" value="66" would have the same effect on the meter.

Here’s the code for today’s post on my GitHub-https://github.com/mfletcher2021/blogcode/blob/main/coolhtmlthings.html.

Thanks for reading!

Michael

static void C# method: (lesson on methods)

Advertisements

Hello everyone,

Michael here, and in today’s post, we’re going to build upon what we learned in the previous post on C# classes and objects-new C# class.

In this post, we’ll use the same base code as we did in the previous post, but we’re going to build upon what we’ve learned by adding methods into the classes we’ve built!

What is a C# method?

Before we actually get into the method-making, as a quick refresher, what is a method? In programming, a method is a set of instructions for a program to execute when the method is called in the script.

static void C# method

With that quick refresher, let’s dive right in to creating C# methods. We’ll add our methods to the https://github.com/mfletcher2021/blogcode/blob/main/Cat.cs script we developed in the previous post.

First off, let’s explore a simple static void () method.

static void meow()
{
   Console.WriteLine("meowmeowmeowmeow");
}

In this simple method, I’m telling the console to output the line "meowmeowmeowmeow" every time this method is called.

Now why is it a static void method? static indicates that this method belongs to the Cat class while void indicates that this method doesn’t have a return type.

static void Main()

Yes, as you may have noticed, the Main() method in C# is also a static void method. Why might that be? The Main() method acts as an entry point to execute the program; this method sets variables and executes all the method calls for running the script. Since the Main() method doesn’t return anything, static void() would be the most appropriate type to use for this method.

meow();

So, how do we call the method? Here’s how:

class Cat
{
    string color = "orange";
    int age = 11;
    string gender = "male";

    static void meow()
    {
        Console.WriteLine("meowmeowmeowmeow");
    }

    static void Main(string[] args)
    {
        Cat orangeCat = new Cat();
        Console.WriteLine(orangeCat.age);
        meow();
        meow();

    }
}

11
meowmeowmeowmeow
meowmeowmeowmeow

In the Main() method, simply write the name of the method followed by a semicolon-meow(); in this case. Yes, you can call the method as many times as you want in the Main() method-I did call meow(); twice indicating that there’s a very meowy orange cat.

calculateHumanAge(with parameters)

Now that I’ve shown you how to create a basic method, let’s try something a bit more advanced. This time, let’s see how we can create a method with parameters:

static void calculateHumanAge(int age)
    {
        Console.WriteLine("The cat is " + age * 7 + " in human years.");
    }

In the calculateHumanAge method, I pass in a parameter-int age and output a line that says The cat is (age*7) in human years, which multiplies the number I used in the method by 7.

  • Notice how the method type is still void-this is because we’re not returning anything from the method (Console.WriteLine() isn’t considered a return value). More on return values later.
  • Methods can have as many parameters as you want, but you’ll need to remember to include all of them whenever you call the method. You’ll also need to include value types for each parameter (e.g. string, int and so on).

Let’s try it out in our code!

class Cat
{
    string color = "orange";
    int age = 11;
    string gender = "male";

    static void meow()
    {
        Console.WriteLine("meowmeowmeowmeow");
    }

    static void calculateHumanAge(int age)
    {
        Console.WriteLine("The cat is " + age * 7 + " in human years.");
    }

    static void Main(string[] args)
    {
        Cat orangeCat = new Cat();
        Console.WriteLine(orangeCat.age);
        meow();
        meow();
        calculateHumanAge(11);

    }
}

11
meowmeowmeowmeow
meowmeowmeowmeow
The cat is 77 in human years.

When I call the calculateHumanAge method in the Main method and pass in 11 as a parameter, I get the output The cat is 77 in human years., indicating that the method did multiply the integer parameter by 7 before outputting that string.

string nameBackwards (return something)

Last but not least, let’s examine method return values.

static String nameBackwards(string name)
    {
        char[] nameList = name.ToCharArray();
        String reversedName = String.Empty;

        for (int i = nameList.Length - 1; i >= 0; i--)
        {
            reversedName += nameList[i];
        }

        return reversedName;
    }

In the nameBackwards method, we’ll take a String parameter and return that same string printed backwards. We will accomplish this by first creating a character array consisting of each character in the name string, then creating a reversedName string by essentially writing each character in the nameList array in reverse order. We will then return the reversedName string in the method.

  • Yes, C# has a pretty unusual method to initialize empty strings. Instead of doing something like testString = '' like you would do in Python, you would write something like String testString = String.Empty (special emphasis on the String.Empty part).

Let’s see the output!

class Cat
{
    string color = "orange";
    int age = 11;
    string gender = "male";

    static void meow()
    {
        Console.WriteLine("meowmeowmeowmeow");
    }

    static void calculateHumanAge(int age)
    {
        Console.WriteLine("The cat is " + age * 7 + " in human years.");
    }
    
    static String nameBackwards(string name)
    {
        char[] nameList = name.ToCharArray();
        String reversedName = String.Empty;

        for (int i = nameList.Length - 1; i >= 0; i--)
        {
            reversedName += nameList[i];
        }

        return reversedName;
    }

    static void Main(string[] args)
    {
        Cat orangeCat = new Cat();
        Console.WriteLine(orangeCat.age);
        meow();
        meow();
        calculateHumanAge(11);
        Console.WriteLine(nameBackwards("Simba"));

    }
}

11
meowmeowmeowmeow
meowmeowmeowmeow
The cat is 77 in human years.
abmiS

In this example, I passed in the string Simba as the parameter for the nameBackwards method and got the backwards string abmiS as the return value-yes, the nameBackwards method will keep the upper-and-lower-casing of the original input string intact.

  • Since I didn’t see the output when simply writing nameBackwards("Simba"), I wrapped this method call inside a Console.WriteLine() method call to see the output.

Today’s script can be found on my GitHub at the following link-https://github.com/mfletcher2021/blogcode/blob/main/CatMethods.cs.

Thanks for reading!

Michael