Wiktor Lalik

Project 1

1. Introduction

Dataset topic

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

  1. How many bags do vendors report in a typical filing?
  2. Do bag counts differ by state?
  3. 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 libraries
library(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.
# look at the columns and their types
glimpse(bag)
Rows: 79,642
Columns: 14
$ `File ID`          <dbl> 101607, 101908, 101750, 102257, 101640, 101653, 101…
$ Account            <chr> "000252", "001400", "005209", "000769", "000163", "…
$ `Date From`        <chr> "10/01/2026", "10/01/2026", "10/01/2026", "10/01/20…
$ `Date To`          <chr> "12/31/2026", "12/31/2026", "12/31/2026", "12/31/20…
$ `Bag Count`        <dbl> 6082, 4538, 2199, 1239, 2504, 3250, 748, 1309, 8438…
$ `Amount Collected` <chr> "$608.20", "$453.80", "$219.90", "$123.90", "$250.4…
$ `Amount Due`       <chr> "$304.10", "$226.90", "$109.95", "$61.95", "$125.20…
$ `Amount Retained`  <chr> "$304.10", "$226.90", "$109.95", "$61.95", "$125.20…
$ `Create Date`      <chr> "02/03/2026", "03/02/2026", "02/17/2026", "04/10/20…
$ `Vendor Name`      <chr> "Takoma Park Silver Spring Cooperative Inc.", "iISL…
$ City               <chr> "Takoma Park", "Gaithersburg", "Kensington", "Silve…
$ State              <chr> "MD", "MD", "MD", "MD", "PA", "MD", "MD", "CA", "GA…
$ `Zip code`         <chr> "20912", "20877", "20895", "20910", "15203", "20855…
$ Location1          <chr> "Takoma Park MD 20912 (38.98126, -77.00988)", "Gait…
# First few rows
head(bag)
# A tibble: 6 × 14
  `File ID` Account `Date From` `Date To`  `Bag Count` `Amount Collected`
      <dbl> <chr>   <chr>       <chr>            <dbl> <chr>             
1    101607 000252  10/01/2026  12/31/2026        6082 $608.20           
2    101908 001400  10/01/2026  12/31/2026        4538 $453.80           
3    101750 005209  10/01/2026  12/31/2026        2199 $219.90           
4    102257 000769  10/01/2026  12/31/2026        1239 $123.90           
5    101640 000163  10/01/2026  12/31/2026        2504 $250.40           
6    101653 003608  10/01/2026  12/31/2026        3250 $325.00           
# ℹ 8 more variables: `Amount Due` <chr>, `Amount Retained` <chr>,
#   `Create Date` <chr>, `Vendor Name` <chr>, City <chr>, State <chr>,
#   `Zip code` <chr>, Location1 <chr>

3. Cleaning

Rename columns and convert money text to numbers

# Rename columns so they have no spaces
bag <- 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 numbers
bag <- 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 year
         date_from = as.Date(date_from, format = "%m/%d/%Y"),
         year = format(date_from, "%Y"))

# Count missing values in each column
colSums(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 states
bag_clean <- bag |>
  filter(bag_count > 0) |>
  filter(state %in% c("MD", "DC", "VA", "PA"))

# Check how many rows remain
count(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 variable
summary(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 state
bag_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 counts
bag_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 values
bag_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 caption
  labs(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 theme
  theme_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)
# A tibble: 6 × 3
  account n_filings total_bags
  <chr>       <int>      <dbl>
1 000001         18        151
2 000012         22      51671
3 000014         59     137748
4 000018        106      90497
5 000022          1         68
6 000024          2       8395

Fit the model

# Linear regression
model <- lm(total_bags ~ n_filings, data = vendor_data)

# Coefficients, p-values and adjusted R-squared
summary(model)

Call:
lm(formula = total_bags ~ n_filings, data = vendor_data)

Residuals:
      Min        1Q    Median        3Q       Max 
 -3974387   -350737     85346    190614 218665788 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -219199     175462  -1.249    0.212    
n_filings      14229       2853   4.987 6.81e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5575000 on 1590 degrees of freedom
Multiple R-squared:  0.0154,    Adjusted R-squared:  0.01478 
F-statistic: 24.87 on 1 and 1590 DF,  p-value: 6.811e-07

Equation

total_bags = -2.191992^{5} + 1.42291^{4} × n_filings

Analysis

  • 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 grid
par(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.