Montgomery County, Maryland charges a 5-cent tax on each carryout bag that a retailer gives to a customer. Each quarter, vendors file a report with the county. This dataset contains those filings. Each row is one filing by one vendor.
Variables
Account – the vendor’s account number
Date From / Date To – the start and end of the reporting period
Bag Count – number of taxable bags the vendor reported
Amount Collected – tax money collected from customers (in dollars)
Amount Due – the part of the tax owed to the county
Amount Retained – the part of the tax the vendor keeps
Create Date – the date the filing was created
Vendor Name, City, State, Zip code – who and where the vendor is
What I plan to explore
How many bags do vendors report in a typical filing?
Do bag counts differ by state?
Can the number of filings a vendor makes predict its total bags (linear regression)?
Source
Montgomery County Government (Maryland), Department of Finance, “Bag Tax” dataset, published on the county’s open data portal, dataMontgomery (data.montgomerycountymd.gov). File used: Bag_Tax_20261009.csv.
2. Setup
Load libraries
# load librarieslibrary(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.1 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.3 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readr)
Load the data
# Read the csv with readr::read_csv()bag <- readr::read_csv("Bag_Tax_20261009.csv")
Rows: 79642 Columns: 14
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (12): Account, Date From, Date To, Amount Collected, Amount Due, Amount ...
dbl (2): File ID, Bag Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Rename columns so they have no spacesbag <- bag |>rename(bag_count =`Bag Count`,collected =`Amount Collected`,due =`Amount Due`,retained =`Amount Retained`,vendor =`Vendor Name`,date_from =`Date From`,state = State,account = Account)# The money columns are text like "$608.20", so remove the "$" and "," and make them numbersbag <- bag |>mutate(collected =as.numeric(gsub(",", "", gsub("$", "", collected, fixed =TRUE), fixed =TRUE)),due =as.numeric(gsub(",", "", gsub("$", "", due, fixed =TRUE), fixed =TRUE)),retained =as.numeric(gsub(",", "", gsub("$", "", retained, fixed =TRUE), fixed =TRUE)),# turn the reporting start date into a real date, then pull out the yeardate_from =as.Date(date_from, format ="%m/%d/%Y"),year =format(date_from, "%Y"))# Count missing values in each columncolSums(is.na(bag))
File ID account date_from Date To bag_count collected
0 0 0 0 0 0
due retained Create Date vendor City state
0 0 0 0 24911 0
Zip code Location1 year
0 0 0
# Keep only filings with at least one bag, and the four most common statesbag_clean <- bag |>filter(bag_count >0) |>filter(state %in%c("MD", "DC", "VA", "PA"))# Check how many rows remaincount(bag_clean, state)
# A tibble: 4 × 2
state n
<chr> <int>
1 DC 248
2 MD 55150
3 PA 1982
4 VA 1841
4. Exploring the data
Summary of bag counts
# statistics for the main variablesummary(bag_clean$bag_count)
Min. 1st Qu. Median Mean 3rd Qu. Max.
1 262 962 8336 2863 10000000
# Average and median bags per filing, by statebag_clean |>group_by(state) |>summarize(n_filings =n(),mean_bags =mean(bag_count),median_bags =median(bag_count),.groups ="drop")
# A tibble: 4 × 4
state n_filings mean_bags median_bags
<chr> <int> <dbl> <dbl>
1 DC 248 1809. 1793
2 MD 55150 4403. 920
3 PA 1982 117710. 2689
4 VA 1841 9303. 879
histogram
# Histogram of bag countsbag_clean |>filter(bag_count <20000) |>ggplot(aes(x = bag_count)) +geom_histogram(binwidth =500) +labs(title ="Most filings report a small number of bags",x ="Bags reported in one filing",y ="Number of filings")
5. Main visualization
Bag counts by state
# box plots of bag counts for each state.# Filings under 10,000 bags are shown so the boxes are not affected by extreme valuesbag_clean |>filter(bag_count <10000) |>ggplot(aes(x = state, y = bag_count, fill = state)) +geom_boxplot() +scale_fill_manual(values =c("MD"="green", "DC"="yellow","VA"="purple", "PA"="red")) +# title, axis labels, legend title and source captionlabs(title ="Bags Reported per Filing by Vendor State",subtitle ="Montgomery County, MD bag tax filings (under 10,000 bags)",x ="Vendor state",y ="Bags reported in one filing",fill ="State",caption ="Source: Montgomery County Government (MD), Department of Finance, dataMontgomery") +# change the default ggplot themetheme_minimal()
6. Linear regression
# For each vendor, count the filings and add up all the bags they reported.vendor_data <- bag_clean |>group_by(account) |>summarize(n_filings =n(),total_bags =sum(bag_count),.groups ="drop")head(vendor_data)
p-value: the number of filings is a statistically significant predictor of total bags.
Adjusted R²: The model is significant but a weak predictor.
Diagnostic plots: The assumptions are not met. The residuals are not random, because they fan out as the number of filings grows.
Diagnostic plots
# Show all four diagnostic plots in a 2 x 2 gridpar(mfrow =c(2, 2))plot(model)
7. Conclusion
Bag counts per filing are very skewed: most filings are small, and the median is highest for Pennsylvania and DC, compared with Maryland and Virginia. A few very large filings push Pennsylvania’s average far above its median, so the median describes a typical filing better than the mean. The regression shows that the number of filings is a statistically significant predictor of a vendor’s total bags (p < 0.001), but it is a weak model: the adjusted R² is only about 0.015, so the number of filings explains about 1.5% of the variation in total bags. One limitation is that the data only covers vendors who filed a report, and a small number of very large vendors have a big influence on the totals.
Cleaning the data
The file had 79,642 rows. I loaded it with readr::read_csv() and used glimpse() to check the variable types. The dollar columns were read in as text, like “$608.20”. I used rename() to shorten the column names, then mutate() with gsub() to remove the “$” and “,” and as.numeric() to make them numbers. I also changed the start date to a real date with as.Date() and checked for missing values with colSums(is.na()). Finally, I used filter() to keep filings with more than zero bags from the four most common states (MD, DC, VA, PA), which left 59,221 filings.
The visualization
My box plots show how many bags vendors reported per filing in each state. Maryland and Virginia have low medians (about 920 and 879 bags), while Pennsylvania and DC are higher (about 2,689 and 1,793). The surprise was that Pennsylvania’s mean (about 117,000 bags) is far above its median, because a few huge filings pull the average up. I only plotted filings under 10,000 bags so the extreme values wouldn’t squash the boxes.
Challenges
One challenge was that the dollar columns were stored as text, because they included “$” and “,” characters, so I couldn’t do any math with them. I fixed this by removing those characters with gsub() and then converting the columns with as.numeric(). Another challenge was that column names like Bag Count contain spaces, which require backticks in R, so I used rename() to give them shorter names.