Chart AI Adoption by Company Size
Rebuild a Census AI adoption chart while showing survey wording breaks.
Introduction
30 Second Summary
A chart can look convincing while hiding a change in what the survey actually asked. That wording shift can turn a measurement break into a dramatic trend.
In this project, you will rebuild a published chart of US company AI adoption using public data from the US Census Bureau. You will find the survey wording change before showing the results as separate series.
What You'll Build
Your finished chart will show AI adoption by company size with a visible break where the survey question changed.
By the end of this project, you'll have:
- A repeatable notebook that filters public survey data to companies with 250 or more employees.
- A before-and-after comparison that reveals why the apparent jump in adoption needs scrutiny.
- A publication-ready chart with separate lines for each question wording. Its six-survey rolling averages stop at the wording break. A footnote explains the method.
- Secret Mission: An optional challenge to extend the analysis while preserving its honest treatment of survey changes.
Are there any prerequisites?
You need a browser plus basic Python reading skills. No paid tools are required because Google Colab can run the notebook with public data.
Before We Start
Rebuilding the chart starts with a place to run the analysis. It also needs a public home for the evidence behind the finished image.
Google Colab gives you a hosted Jupyter Notebook environment. Your public GitHub repository becomes the project folder.
You will confirm that the hosted runtime can load pandas plus Matplotlib. These libraries handle the survey table and its chart.
In this step, get ready to:
- Create a public GitHub repository for the project.
- Confirm that pandas plus Matplotlib load in Google Colab.
- Save the notebook in the public repository.
Create the public project repository
A repository records each version of your project files. Public visibility also lets readers inspect the notebook behind your finished chart.
- Open GitHub's new repository page in your browser.
- Sign in to GitHub if prompted.
- Use the sign-in page to create an account if needed.
You will see a form for defining the repository and its visibility.
- Select your personal account from the Owner menu.
- Enter census-ai-adoption-chart in the Repository name field.
- Enter Reproducible chart of US company AI adoption from Census Bureau survey data. in the Description field.
- Select Public as the repository visibility.
- Toggle Add README to on.
- Click Create repository.
You will see README.md in the repository file list. The repository page also shows its public visibility.
Good progress. Your project now has a public folder for every artifact you produce.
Repository creation blocked?
- Choose a different available repository name if GitHub rejects the first one.
- Confirm that your selected owner allows public repository creation.
Ask for help with the repository form.
Start the hosted notebook
A hosted notebook keeps your Python code beside its output. The runtime supplies the environment that executes each cell.
- Open the Google Colab introduction notebook in your browser.
You will see a notebook running inside the Colab interface.
- Click File in the top menu.
- Select New notebook.
You will see a blank notebook with an empty code cell.
- Select the first code cell.
- Paste the library check into the selected cell:
# Load the table and chart libraries used throughout the project
import pandas as pd
import matplotlib.pyplot as plt
# Confirm both imports completed without an error
print("pandas and matplotlib are ready")
What Does This Check Prove?
- The pandas import makes table-processing tools available through pd.
- The matplotlib.pyplot import makes chart-building tools available through plt.
- The final message runs only after both imports complete successfully.
Before you run the cell, do you expect both libraries to load without an import error?
- Run the cell with the play button beside it.
You will see pandas and matplotlib are ready below the cell.
That is your first working result. The notebook can now process tables and draw charts.
Imports Not Completing?
- Confirm that the hosted runtime is connected before running the cell again.
- Reconnect the runtime if the notebook lost its session.
Help me diagnose the failed imports.
- Click the notebook title at the top of the page.
- Replace the title with census_ai_adoption.ipynb.
The new notebook name now appears at the top of the Colab page.
✔️ Awesome, I've got everything!
Your notebook contains the working library check. Its project filename is visible at the top.
ⓧ I'd like to double check the full code
# Load the table and chart libraries used throughout the project
import pandas as pd
import matplotlib.pyplot as plt
# Confirm both imports completed without an error
print("pandas and matplotlib are ready")
Publish the notebook to GitHub
Saving the notebook to GitHub places your executable analysis beside its future data and chart files. The repository then becomes the public record of your method.
- Click File in the Colab menu.
- Select Save a copy to GitHub.
You will see a publishing prompt for choosing where Colab should save the notebook.
The authorization request can feel sensitive. Colab needs repository access so it can push this notebook to GitHub.
- Approve the authorization request if Colab displays one.
- Choose census-ai-adoption-chart from the repository selector.
The publishing prompt now shows your project repository as the destination.
- Enter Add notebook setup as the commit message.
- Confirm the save from the publishing prompt.
Before you check GitHub, which two files do you expect to find in the repository?
- Switch back to the GitHub repository from earlier.
- Refresh the repository page.
You will see README.md plus census_ai_adoption.ipynb in the file list. The repository remains marked Public.
Your setup is complete. The notebook runs both chart libraries from a public project home.
Notebook Missing From GitHub?
- Confirm that the publishing prompt targeted census-ai-adoption-chart.
- Repeat the save after approving Colab's repository permission.
- Refresh the GitHub file list after the save finishes.
Help me publish the notebook.
Your notebook environment and public project folder are ready. Next up, you will bring the Census Bureau workbook into the notebook.
Download and Open the Survey Workbook
Your public Git repository is ready. A trustworthy chart starts with the exact workbook behind the published result.
Pandas exposes the workbook structure before filtering changes the evidence. The notebook also imports Matplotlib now for the charting work ahead.
In this step, get ready to:
- Download the Employment Size Class workbook from the source post.
- Display the workbook's available worksheet names.
- Inspect the raw survey table after loading it into pandas.
Download the source workbook
The source post provides a snapshot of the workbook used for the published chart. Saving that snapshot with a stable local filename keeps every notebook cell connected to the same evidence.
- Open the author's workbook copy in your browser.
- Rename the downloaded file to employment_size_class.xlsx.
- Move employment_size_class.xlsx into the working project folder from earlier.
- Confirm that employment_size_class.xlsx is listed in that folder.
Workbook missing from your project folder?
- Check your browser's downloads location for the original workbook filename.
- Confirm that the renamed file ends with .xlsx.
Help me find and rename the downloaded workbook.
Create the inspection notebook
An Excel workbook can contain several worksheets with different roles. Listing those worksheet names first gives you a map of the file before you select any data.
- Switch back to the notebook environment from earlier.
- Create a notebook inside the working project folder with your environment's new-notebook control.
- Name the notebook ai_adoption_chart.ipynb.
- Add a code cell at the top of ai_adoption_chart.ipynb.
- Import the project libraries by pasting this code into the cell:
# Import the tools used throughout the notebook
import pandas as pd
import matplotlib.pyplot as plt
# Point pandas at the workbook saved beside this notebook
workbook_path = "employment_size_class.xlsx"
workbook = pd.ExcelFile(workbook_path)
# Print every worksheet before choosing a table
print(workbook.sheet_names)
What does this code do?
- The imports make pandas available as pd.
- The imports make Matplotlib available as plt for later charting.
- The workbook_path variable keeps the workbook filename in one place.
- The pd.ExcelFile() call reads the workbook's structure without changing its contents.
- The sheet_names property reveals the worksheets available for analysis.
- Save ai_adoption_chart.ipynb.
- Run the first code cell.
You'll see a Python list containing the workbook's worksheet names. This confirms that pandas can open the downloaded file.
Workbook not opening?
- Check that the notebook sits in the same project folder as employment_size_class.xlsx.
- Check the workbook filename for spaces or capital letters that do not match workbook_path.
- Use your notebook environment's package manager if pandas reports that its Excel reader dependency is missing.
Help me diagnose why pandas cannot open my workbook.
Inspect the raw survey table
A DataFrame turns the worksheet into a table that Python can inspect. The column labels show how the workbook organizes its fields.
- Add a new code cell below the worksheet-name output.
- Load the raw table by pasting this code into the new cell:
# Load the workbook's first worksheet as the raw survey table
raw_survey = pd.read_excel(workbook_path)
# Print the column labels before previewing the observations
print(raw_survey.columns.tolist())
# Display the first five rows without changing the source data
raw_survey.head()
What does this code do?
- The pd.read_excel() call loads the workbook's default worksheet into raw_survey.
- The columns.tolist() call prints the table's column labels in reading order.
- The head() call previews five raw rows without modifying the workbook.
- Save ai_adoption_chart.ipynb.
Before you run this cell, what do you expect the raw preview to reveal about the survey table's structure?
- Run the second code cell.
You'll see a list of column labels followed by five raw survey rows. That output proves the workbook is ready for analysis.
Strong start. Your notebook now exposes the original table before any filtering can hide important context.
Raw rows not appearing?
- Run the first code cell again if the notebook does not recognize workbook_path.
- Check that the second cell uses raw_survey consistently.
- Confirm that the code cell finishes without a file-reading error.
Help me fix my raw survey preview.
✔️ Awesome, I've got everything!
Both workbook-inspection cells are in place.
- Save ai_adoption_chart.ipynb once more.
ⓧ I'd like to double check the full code
- Compare the two cells in ai_adoption_chart.ipynb with this cumulative code.
# Import the tools used throughout the notebook
import pandas as pd
import matplotlib.pyplot as plt
# Point pandas at the workbook saved beside this notebook
workbook_path = "employment_size_class.xlsx"
workbook = pd.ExcelFile(workbook_path)
# Print every worksheet before choosing a table
print(workbook.sheet_names)
# Load the workbook's first worksheet as the raw survey table
raw_survey = pd.read_excel(workbook_path)
# Print the column labels before previewing the observations
print(raw_survey.columns.tolist())
# Display the first five rows without changing the source data
raw_survey.head()
Your source data is downloaded and visible in the notebook. Next up, you'll isolate the AI-use question for companies with at least 250 employees.
Filter the AI Adoption Series
You have the workbook open. You also know how its survey rows are arranged. The chart still cannot use that raw table because it mixes many questions with several answers and company sizes.
In this step, you will isolate current AI use reported by firms with 250 or more employees. You will reshape those estimates into a chronological DataFrame for the chart.
In this step, get ready to:
- Match the survey rows for current AI use.
- Keep the employment-size category for firms with 250 or more employees.
- Prepare the survey dates and estimates as one chronological series.
Filter the current-use rows
The response sheet includes a current-use question and a six-month outlook question. Matching the opening phrase keeps current use while preserving the complete question wording for later analysis.
- Switch back to the open ai_adoption_chart.ipynb notebook.
- Add a new code cell below the raw-row preview.
- Create the current-use filter for the largest employment-size category by running the code below:
# Match the current-use question while excluding the six-month outlook
is_current = (
resp["Question"]
.astype(str)
.str.strip()
.str.startswith("In the last two weeks")
)
# Keep yes estimates for firms in the largest employment-size category
ai_yes_250 = resp[
is_current
& resp["Answer"].astype(str).str.strip().str.lower().eq("yes")
& resp["Empsize"].astype(str).str.strip().eq("G")
].copy()
# Preview the identifiers retained by the filter
ai_yes_250[["Question", "Answer", "Empsize"]]
What does this filter do?
- The is_current mask selects questions beginning with In the last two weeks. This excludes the future-use question.
- The Answer condition keeps estimates for businesses that answered yes.
- The Empsize value G identifies firms with 250 or more employees.
- Save ai_adoption_chart.ipynb.
- Check the cell output. You should see only rows with yes in Answer and G in Empsize.
Seeing an empty filter?
Check that resp contains the Response Estimates sheet. Compare the capitalization of Question, Answer, and Empsize with the columns you inspected earlier.
If the filter is still empty, help me debug the AI-use row filter.
Reshape the estimates by date
Each survey period currently occupies its own column. Reshaping turns those columns into rows. Joining the date sheet then gives every estimate a calendar date.
- Add a new code cell below the filtered-row preview.
- Build the chronological AI adoption series by running the code below:
# Load the workbook's lookup between survey periods and reference dates
dates = pd.read_excel(
"employment_size_class.xlsx",
sheet_name="Collection and Reference Dates",
)
# Identify the six-digit survey-period columns
period_cols = [
column
for column in ai_yes_250.columns
if str(column).isdigit() and len(str(column)) == 6
]
# Turn the period columns into one row per estimate
ai_adoption = ai_yes_250.melt(
id_vars=["Question"],
value_vars=period_cols,
var_name="Smpdt",
value_name="estimate",
)
# Attach dates and prepare clean values for chronological analysis
dates["Smpdt"] = dates["Smpdt"].astype(str)
ai_adoption["Smpdt"] = ai_adoption["Smpdt"].astype(str)
ai_adoption = ai_adoption.merge(
dates[["Smpdt", "Ref End"]],
on="Smpdt",
how="left",
)
ai_adoption["date"] = pd.to_datetime(ai_adoption["Ref End"], errors="coerce")
ai_adoption["estimate"] = pd.to_numeric(
ai_adoption["estimate"].astype(str).str.replace("%", "", regex=False).str.strip(),
errors="coerce",
)
ai_adoption = (
ai_adoption[["date", "estimate", "Question"]]
.dropna(subset=["date", "estimate"])
.sort_values("date")
.reset_index(drop=True)
)
# Preview the earliest usable estimates
ai_adoption.head()
How does the table change?
- The period_cols list finds the columns that represent survey periods.
- The melt() operation converts the wide survey table into one estimate per row.
- The merge uses Smpdt to attach the reference end date from the workbook.
- The final chain removes unusable values. It also sorts the remaining observations from earliest to latest.
- Save ai_adoption_chart.ipynb.
- Check the preview. You should see date, estimate, and Question columns ordered from the earliest survey date.
Missing dates or estimates?
Confirm that period_cols contains six-digit survey period labels. An empty list means the period columns use a different data type or format.
Confirm that the date sheet contains Smpdt and Ref End. If values remain missing, help me inspect the period-to-date merge.
Verify the chronological series
Before you run this check, do you expect the filtered table to contain usable estimates in chronological order?
- Test the filtered series by running this code in a new notebook cell:
# Confirm the filter produced usable chronological observations
assert not ai_adoption.empty
assert ai_adoption["date"].is_monotonic_increasing
assert ai_adoption["estimate"].notna().all()
# Compare the beginning and end of the prepared series
print(f"{len(ai_adoption)} usable estimates")
display(ai_adoption.head(3))
display(ai_adoption.tail(3))
What does this check prove?
- The assertions confirm that the filtered series contains rows. They also confirm that every estimate is usable.
- The chronological assertion confirms that each date follows the previous date.
- The previews show the earliest and latest observations. You should see a positive estimate count followed by two dated table previews.
- Save ai_adoption_chart.ipynb.
- Confirm that the cell finishes without an assertion failure.
- Compare the first preview with the second preview. The dates in the second preview should be later.
Did an assertion stop the cell?
An empty result points back to the question or employment-size filter. A date-order problem points to the final sort_values() operation.
For a focused diagnosis, help me identify which DataFrame assertion failed.
That is the crowded survey workbook reduced to the exact company-size series you need.
Your filtering cells should now match this checkpoint.
✔️ Awesome, I've got everything!
Your notebook now contains a chronological AI-use series for firms with 250 or more employees.
ⓧ I'd like to double check the full code
import pandas as pd
import matplotlib.pyplot as plt
# Load the workbook and inspect its raw survey structure
workbook_path = "employment_size_class.xlsx"
excel_file = pd.ExcelFile(workbook_path)
display(excel_file.sheet_names)
resp = pd.read_excel(workbook_path, sheet_name="Response Estimates")
display(resp.columns.tolist())
display(resp.head())
# Match the current-use question while excluding the six-month outlook
is_current = (
resp["Question"]
.astype(str)
.str.strip()
.str.startswith("In the last two weeks")
)
# Keep yes estimates for firms in the largest employment-size category
ai_yes_250 = resp[
is_current
& resp["Answer"].astype(str).str.strip().str.lower().eq("yes")
& resp["Empsize"].astype(str).str.strip().eq("G")
].copy()
# Preview the identifiers retained by the filter
ai_yes_250[["Question", "Answer", "Empsize"]]
# Load the workbook's lookup between survey periods and reference dates
dates = pd.read_excel(
"employment_size_class.xlsx",
sheet_name="Collection and Reference Dates",
)
# Identify the six-digit survey-period columns
period_cols = [
column
for column in ai_yes_250.columns
if str(column).isdigit() and len(str(column)) == 6
]
# Turn the period columns into one row per estimate
ai_adoption = ai_yes_250.melt(
id_vars=["Question"],
value_vars=period_cols,
var_name="Smpdt",
value_name="estimate",
)
# Attach dates and prepare clean values for chronological analysis
dates["Smpdt"] = dates["Smpdt"].astype(str)
ai_adoption["Smpdt"] = ai_adoption["Smpdt"].astype(str)
ai_adoption = ai_adoption.merge(
dates[["Smpdt", "Ref End"]],
on="Smpdt",
how="left",
)
ai_adoption["date"] = pd.to_datetime(ai_adoption["Ref End"], errors="coerce")
ai_adoption["estimate"] = pd.to_numeric(
ai_adoption["estimate"].astype(str).str.replace("%", "", regex=False).str.strip(),
errors="coerce",
)
ai_adoption = (
ai_adoption[["date", "estimate", "Question"]]
.dropna(subset=["date", "estimate"])
.sort_values("date")
.reset_index(drop=True)
)
# Preview the earliest usable estimates
ai_adoption.head()
# Confirm the filter produced usable chronological observations
assert not ai_adoption.empty
assert ai_adoption["date"].is_monotonic_increasing
assert ai_adoption["estimate"].notna().all()
# Compare the beginning and end of the prepared series
print(f"{len(ai_adoption)} usable estimates")
display(ai_adoption.head(3))
display(ai_adoption.tail(3))
Your single company-size series is ready. Next up, you will plot its raw estimates and see the pattern they appear to tell.
Plot the Raw Adoption Estimates
Your notebook now holds one chronological series for companies with 250 or more employees. That careful filtering keeps the next picture focused on a single company-size group.
A line chart turns those survey estimates into a visual trend. You will use Matplotlib to test the story created when every survey point shares one uninterrupted line.
In this step, get ready to:
- Plot the raw survey estimates as one continuous line.
- Inspect the largest change between neighboring survey points.
Create the baseline line
Connecting every point treats adjacent surveys as one continuous measurement. This familiar chart gives you a baseline for judging the visual story.
- Return to the open ai_adoption_chart.ipynb notebook from the previous step.
- Add a code cell immediately below the cell that displays the chronological filtered data.
- Write a single Matplotlib line-plot expression as the last line of the new cell. Use the existing survey-date column for the horizontal values. Use the existing estimate column for the vertical values. Keep every chronological point on one line.
Why start with raw estimates?
Raw estimates preserve every survey result before smoothing changes the shape. Their point-to-point movement makes abrupt changes easy to spot.
Run and inspect the chart
Before you run the cell, which pair of neighboring survey dates do you expect to have the largest vertical change?
- Run the raw-chart cell using your notebook's run control.
- Inspect the line for its steepest section.
You will see one continuous line with a pronounced jump between neighboring survey dates. The picture makes that jump look like sudden adoption growth.
What Does the Raw Line Claim?
The line presents every neighboring estimate as directly comparable. Its steep segment invites readers to interpret the jump as a real change in company behavior.
Can't see the raw line?
Check that the plotting expression reads from the filtered company-size series. Confirm that survey dates supply the horizontal values.
Rerun the filtering cell if the notebook cannot access the filtered data. Help me debug my raw chart.
- Save ai_adoption_chart.ipynb using your notebook's save control.
Your saved notebook now contains the filtered series plus its raw one-line chart. You have reproduced the apparent jump that a reader could mistake for sudden adoption growth.
Next, you will test whether every survey point belongs on the same continuous line.
Separate the Question Wordings
The raw Matplotlib chart from the previous step made the apparent AI adoption jump visible. That line joins estimates collected under two different Census question wordings.
In this step, you will separate the chronological DataFrame at the November 2025 wording change.
Each wording receives its own line. Readers can see where continuity ends.
In this step, get ready to:
- Locate the November 2025 question-wording break in the chronological survey data.
- Split the filtered series into separate DataFrames for the two wording periods.
- Plot the two raw series without connecting them across the break.
Locate the wording break
The first wording period contains survey estimates before November 2025. The second wording period begins with estimates from November 2025 onward.
Why must the line break?
Before November 2025, the survey question focused on AI use in producing goods or services. From November 2025, the question covered AI use in any business function.
The broader scope changes what the percentage measures. A visible break prevents readers from interpreting the jump as pure adoption growth.
- Switch back to the open ai_adoption_chart.ipynb notebook.
- Run the cell that displays the filtered chronological table.
- Find the final survey date that falls before November 2025.
- Find the first survey date that falls in November 2025 or later.
Those dates define the two sides of the break. Every row belongs to exactly one wording period.
Split the series at November 2025
A pandas date filter can isolate each wording period. Complementary filters keep every estimate while preventing overlap.
How should the boundary work?
The first filter keeps dates before November 2025. The second filter keeps dates in November 2025 or later.
These conditions assign the boundary to the second wording period. They also prevent a survey row from disappearing.
- Create the pre-November-2025 DataFrame in a new code cell by filtering the survey-date column to rows before November 2025.
- Leave that DataFrame's name as the cell's final expression.
- Run the cell.
You should see only survey rows dated before November 2025. The final displayed row should remain on the first side of the wording break.
- Create the November-2025-and-later DataFrame in a second code cell by filtering the survey-date column to rows in November 2025 or later.
- Leave the second DataFrame's name as the cell's final expression.
- Run the cell.
You should see only survey rows from November 2025 onward. The first displayed row should belong to the changed wording period.
Seeing rows on the wrong side?
Confirm that the survey-date column still contains dates. Text values can produce an incorrect chronological comparison.
Check that the first filter means before November 2025. Check that the second filter includes November 2025.
If the boundary still looks wrong, use help me inspect my date filters.
Plot the wording series separately
Matplotlib draws continuous segments between points sent to one plotting call. Giving each DataFrame its own plotting call keeps the survey periods visually separate.
- Return to the raw one-line plotting cell from the previous step.
- Replace the single plotting call with one plotting call for each wording DataFrame.
- Keep the existing survey-date x column and estimate y column in both calls.
Before you run the plotting cell, ask yourself where the first line stops and the second line begins.
- Run the plotting cell.
You should see two raw lines. The first stops before November 2025.
The second starts in November 2025 or later. No segment joins the two wording periods.
Still seeing one connected line?
Check that the original all-rows plotting call has been removed. Each replacement call should receive only one wording DataFrame.
Use help me trace the connected line if a segment still crosses the break.
- Save ai_adoption_chart.ipynb.
You have replaced a misleading connection with an honest break in the data. Next, you will smooth each wording series without averaging across that boundary.
Calculate Six-Survey Rolling Averages
Your raw chart already shows the November 2025 question break honestly. Its survey estimates still bounce between releases.
A six-survey rolling mean reduces that short-term movement. Calculating it independently inside each wording period prevents observations from opposite sides of the break from entering the same average.
In this step, get ready to:
- Calculate a six-survey rolling mean inside each wording period.
- Inspect where each complete six-survey window begins.
- Plot the separate smoothed series without crossing the wording break.
Calculate each wording's rolling mean
A six-survey rolling mean in Pandas uses the current estimate plus the previous five estimates. The first five rows in each wording period stay empty because they do not contain a complete six-survey window.
- Return to the split-series cell in ai_adoption_chart.ipynb.
- Record the variable name for the pre-November-2025 DataFrame: pre-November-2025 DataFrame name.
- Record the variable name for the November-2025-and-later DataFrame: November-2025-and-later DataFrame name.
- Record the exact column name that contains the survey estimate: estimate column name.
- Add a new code cell beneath the split-series cell.
- Calculate both independent rolling means by pasting this code into the new cell:
# Calculate each six-survey mean inside its own wording period
[[PRE_WORDING_DF="pre-November-2025 DataFrame name"]]["rolling_mean"] = [[PRE_WORDING_DF="pre-November-2025 DataFrame name"]]["[[ESTIMATE_COLUMN="estimate column name"]]"].rolling(window=6).mean()
[[POST_WORDING_DF="November-2025-and-later DataFrame name"]]["rolling_mean"] = [[POST_WORDING_DF="November-2025-and-later DataFrame name"]]["[[ESTIMATE_COLUMN="estimate column name"]]"].rolling(window=6).mean()
# Preview the opening rows to reveal where each complete window begins
(
[[PRE_WORDING_DF="pre-November-2025 DataFrame name"]][["[[ESTIMATE_COLUMN="estimate column name"]]", "rolling_mean"]].head(8),
[[POST_WORDING_DF="November-2025-and-later DataFrame name"]][["[[ESTIMATE_COLUMN="estimate column name"]]", "rolling_mean"]].head(8),
)
What does this code do?
- The rolling(window=6).mean() calculation takes the arithmetic mean of each complete six-survey window.
- Each assignment stays inside one wording DataFrame. This prevents a window from crossing the November 2025 break.
- The head(8) previews expose the start of each independent rolling series.
- Save ai_adoption_chart.ipynb with Cmd+S on macOS or Ctrl+S on Windows.
Before you run this cell, which row do you expect to hold the first non-empty rolling mean in each table? Lock in your prediction.
- Run the rolling-average cell with your notebook's run control.
You will see NaN in the first five rolling-mean rows of each table. The sixth row contains that wording period's first complete average.
You have cleared the key methodological hurdle. Each wording period now starts its own six-survey window.
Rolling means not appearing?
- If Python reports an unknown DataFrame name, compare the name with the split-series cell above.
- If Python cannot find the estimate column, copy its capitalization exactly from the raw chart cell.
If the cell still fails, help me fix my independent six-survey rolling means.
✔️ Awesome, I've got everything!
Your rolling-average cell now calculates an independent six-survey window for each wording period.
ⓧ I'd like to double check the full code
Compare your complete rolling-average cell with this reference.
# Calculate each six-survey mean inside its own wording period
[[PRE_WORDING_DF="pre-November-2025 DataFrame name"]]["rolling_mean"] = [[PRE_WORDING_DF="pre-November-2025 DataFrame name"]]["[[ESTIMATE_COLUMN="estimate column name"]]"].rolling(window=6).mean()
[[POST_WORDING_DF="November-2025-and-later DataFrame name"]]["rolling_mean"] = [[POST_WORDING_DF="November-2025-and-later DataFrame name"]]["[[ESTIMATE_COLUMN="estimate column name"]]"].rolling(window=6).mean()
# Preview the opening rows to reveal where each complete window begins
(
[[PRE_WORDING_DF="pre-November-2025 DataFrame name"]][["[[ESTIMATE_COLUMN="estimate column name"]]", "rolling_mean"]].head(8),
[[POST_WORDING_DF="November-2025-and-later DataFrame name"]][["[[ESTIMATE_COLUMN="estimate column name"]]", "rolling_mean"]].head(8),
)
Plot the smoothed wording series
The existing Matplotlib chart cell already draws each wording period as a separate line. Changing only its y-value references preserves that separation.
- Replace the y-column reference in the plot call for pre-November-2025 DataFrame name with rolling_mean.
- Replace the y-column reference in the plot call for November-2025-and-later DataFrame name with rolling_mean.
Before you run the chart cell, do you expect either line to bridge the November 2025 break? Lock in your prediction.
- Run the updated chart cell with your notebook's run control.
You will see two smoother lines. No line crosses the November 2025 wording break.
That is the honest smoothed view. Each line begins only after its own wording period supplies six estimates.
- Save ai_adoption_chart.ipynb with Cmd+S on macOS or Ctrl+S on Windows.
Chart still showing raw values?
- Check that both plot calls use rolling_mean for their y-values.
- Check that the chart still contains one plot call for each wording DataFrame.
If the chart still looks wrong, help me debug my separate smoothed lines.
Your rolling averages now respect the survey's wording change. Next up, you will add the chart context that helps readers interpret those lines correctly.
Publish the Finished Chart
Your separate lines already preserve the November 2025 wording break. The six-survey rolling averages now tell an honest story inside the notebook.
Matplotlib places that explanation directly on the figure. Git publishes every project artifact together.
In this step, get ready to:
- Export a labeled chart with a methodology footnote.
- Check the exported chart against the source post.
- Publish every project artifact in your public repository.
Export the final chart
Labels tell readers what the chart measures. The footnote explains why the two rolling averages stay separate.
- Return to the chart cell in ai_adoption_chart.ipynb.
- Paste the block below immediately after the two lines that draw the smoothed series.
- Place the block before any existing line that displays the chart.
# Reuse the chart created by the two smoothed series.
ax = plt.gca()
fig = plt.gcf()
# Add the context readers need to interpret the chart.
ax.set_title("AI Adoption Among US Companies With 250 or More Employees")
ax.set_xlabel("Survey date")
ax.set_ylabel("Companies reporting AI use (%)")
ax.legend(
["Pre-November 2025 wording", "November 2025 and later wording"],
title="Survey wording",
)
# Explain why the rolling averages stay separate at the wording break.
footnote = (
"Note: Six-survey rolling averages are calculated separately for each series "
"because the survey question wording changed in November 2025."
)
fig.text(0.01, 0.01, footnote, ha="left", fontsize=9)
fig.tight_layout(rect=(0, 0.10, 1, 1))
# Export the complete figure before displaying it.
fig.savefig("ai_adoption_by_size.png", dpi=300, bbox_inches="tight")
plt.show()
What does this code do?
- The ax variable holds the plotting area that already contains both smoothed lines.
- The title plus axis labels make the chart understandable outside the notebook.
- The legend names each series according to its survey wording period.
- The footnote keeps the November 2025 methodology break attached to the exported image.
- The fig.savefig() call writes the complete figure to ai_adoption_by_size.png before the chart is displayed.
Before you run the cell, where do you expect the visible gap between the two lines to appear?
- Save ai_adoption_chart.ipynb.
- Run the updated chart cell.
- Confirm that ai_adoption_by_size.png appears in the project folder.
Nice work. That exported chart now carries the wording-break explanation wherever it is shared.
You should see two separate smooth lines with a visible gap at November 2025. Every reader-facing label should be readable.
Is the export missing something?
If the footnote is clipped, confirm that the fig.tight_layout() call matches the block above.
If the image is blank, make sure fig.savefig() comes before plt.show().
Help me debug my exported Matplotlib chart.
✔️ Awesome, I've got everything!
Your final chart cell now labels the figure. It also explains the wording break before exporting the image.
ⓧ I'd like to double check the full code
- Compare the end of your final chart cell with this finishing block.
# Reuse the chart created by the two smoothed series.
ax = plt.gca()
fig = plt.gcf()
# Add the context readers need to interpret the chart.
ax.set_title("AI Adoption Among US Companies With 250 or More Employees")
ax.set_xlabel("Survey date")
ax.set_ylabel("Companies reporting AI use (%)")
ax.legend(
["Pre-November 2025 wording", "November 2025 and later wording"],
title="Survey wording",
)
# Explain why the rolling averages stay separate at the wording break.
footnote = (
"Note: Six-survey rolling averages are calculated separately for each series "
"because the survey question wording changed in November 2025."
)
fig.text(0.01, 0.01, footnote, ha="left", fontsize=9)
fig.tight_layout(rect=(0, 0.10, 1, 1))
# Export the complete figure before displaying it.
fig.savefig("ai_adoption_by_size.png", dpi=300, bbox_inches="tight")
plt.show()
Compare with the source chart
A faithful reproduction preserves the shape of each wording period. It also keeps the methodological break visible.
- Open the source post in a second browser window.
- Keep your finished notebook chart visible beside the source chart.
- Compare the direction of each series within its own wording period.
- Check that neither rolling-average line crosses the November 2025 break.
- Confirm that the title plus footnote identify the company-size group and methodology.
The visual styling can differ. The two wording periods should follow the same broad pattern as the source chart.
Publish the project artifacts
Your public repository is the reproducible record behind the image. It needs all three project artifacts.
Publishing makes the notebook visible to anyone. That openness deserves one quick privacy check before the upload.
- Review ai_adoption_chart.ipynb for personal notes or unrelated information.
- Return to the terminal panel for your public Git repository from earlier.
- Publish the notebook plus workbook plus chart by running these commands:
git add ai_adoption_chart.ipynb employment_size_class.xlsx ai_adoption_by_size.png
git commit -m "Publish AI adoption chart"
git push
What do these commands publish?
- The first command stages the notebook plus source workbook plus exported chart.
- The second command records that exact collection of files in the repository history.
- The final command sends the commit to the configured public remote.
Did the publish stop?
If Git requests authentication, complete the sign-in flow for the repository service before retrying the push.
If Git cannot find one of the files, confirm that the terminal is using the project folder containing ai_adoption_chart.ipynb.
Help me troubleshoot this Git publish. If it still fails, share the non-sensitive error text in the NextWork community.
Before you verify it, do you expect Git to find any local changes left outside the published commit?
- Check the project folder state by running this command:
git status
What should you see?
Git should report that there are no uncommitted changes. That confirms the notebook plus workbook plus chart were included in the commit.
- Visit your public repository page in a browser.
- Confirm that ai_adoption_chart.ipynb is listed.
- Confirm that employment_size_class.xlsx is listed.
- Confirm that ai_adoption_by_size.png is listed.
You have completed the publication loop. Anyone can now inspect the source data plus rerun the notebook plus view the finished chart.
Your chart is published with its evidence and methodology intact. The wording change stays visible wherever the image travels.
Secret mission
Run a Chart Integrity Audit
Rerun your notebook from a clean session. Trace the November 2025 wording break from workbook to chart. Test whether a fresh reader understands why the lines stay separate.
Clean Up Your Resources
Clean Up Your Resources
This project has no ongoing costs because it uses public data in a notebook. Choose whether to keep the project available, pause the notebook session, or remove the work.
Resources you used:
- The public Git repository that publishes your chart project.
- The ai_adoption_chart.ipynb notebook that rebuilds the chart.
- The employment_size_class.xlsx workbook that contains the source survey data.
- The ai_adoption_by_size.png image that contains the finished chart.
- The notebook session used to run the analysis.
Keep everything running
No action is required. This option keeps your chart ready for future survey releases.
- Keep the public Git repository available.
- Keep ai_adoption_chart.ipynb so you can reproduce the analysis.
- Keep employment_size_class.xlsx as the source data behind the chart.
- Keep ai_adoption_by_size.png as the published result.
- Rerun the notebook when the survey publishes estimates for a new wording series.
Pause - I'll come back to this later
Pausing preserves every project file while ending the active notebook session. Your public repository remains available at no ongoing project cost.
- Stop the active notebook execution using your notebook environment's runtime controls.
- Confirm that no notebook cells are still running.
- Leave the public Git repository unchanged.
- Close the notebook tab after the runtime stops.
Delete - I don't want to use this again
Deleting the repository is permanent. A backup gives you a way to recover the project if you change your mind.
- Save a backup copy of the public Git repository outside your notebook environment.
- Stop the active notebook execution using your notebook environment's runtime controls.
- Select ai_adoption_chart.ipynb, employment_size_class.xlsx, plus ai_adoption_by_size.png in your notebook environment's file browser.
- Delete the selected files from the notebook environment.
- Confirm that the three project files are no longer listed in the file browser.
- Open the settings page for the public Git repository to locate your Git host's documented repository deletion control.
- Use that deletion control to remove the public repository.
- Visit the former repository URL to confirm that the project is no longer publicly available.
Nice Work!
Nice Work!
You did it! Your rebuilt Census Bureau AI adoption chart keeps the survey's November 2025 wording break visible.
Your public Git repository preserves the full analysis. Anyone can rerun ai_adoption_chart.ipynb to inspect the source data behind the finished image.
You've learned how to:
- Build a reproducible data workflow with pandas from employment_size_class.xlsx.
- Reveal the misleading jump in a one-line chart by separating the data at the November 2025 wording break.
- Calculate an independent six-survey rolling average for each wording series. Use Matplotlib to export the documented result as ai_adoption_by_size.png.
- Secret Mission: Apply the same wording-aware method in an optional extension that pushes your chart analysis further.
Ready to quiz yourself?