Introduction

Howard County publishes every construction permit it issues. The file covers building, electrical, fire protection, grading, mechanical, plumbing and sign work. I pulled it from the county’s open data portal (opendata.howardcountymd.gov, dataset kvz2-j5cj) on September 8, 2026. Each row is one permit. The file holds 61,857 rows across 11 columns: permit number, category, type, file date, permit type, census tract, issue date, city, ZIP, sewer and water. The data is counts and categories. No continuous variables, no coordinates. That rules out scatterplots and maps. One structural problem shapes everything below: the file contains no records between December 2022 and November 2023.

Data Quality

Twelve months of records are missing, running from December 2022 through November 2023. Counting by year hides this, since the surrounding years look like a collapse and a recovery rather than a hole. Whether the county issued no permits or simply did not publish them, this file cannot say, and the distinction matters because one is an economic finding and the other is a records problem. I excluded 2021 and 2023 because neither year is usable, then trimmed the rest to January through November so 2024 does not carry a December the others lack. That leaves 57,092 permits. Census tract fails separately, with every record issued on or after December 1, 2024 mangled into a timestamp. The city column needed cleaning for case mismatches, three spellings of North Laurel, and one record reading “PROBLEM ADDRESS.”

Descriptive Statistics

data.frame(count(permits_full, CATEGORY))
##      CATEGORY     n
## 1  COMMERCIAL  9159
## 2          MX     1
## 3 RESIDENTIAL 47070
## 4        SIGN   862
data.frame(count(permits_full, TYPE))
##              TYPE     n
## 1        BUILDING 12848
## 2      ELECTRICAL 20381
## 3 FIRE PROTECTION  3787
## 4         GRADING   535
## 5            HVAC  4078
## 6        PLUMBING 14601
## 7            SIGN   862

The working dataset holds 57,092 permits across three years and seven types, and the distribution is heavily skewed. Electrical alone accounts for roughly 36% of the total, and electrical, plumbing and building together account for 84%. Category is coarser, splitting into residential, commercial and sign, with signage its own category because the county permits storefront and business signs separately. One record reads “MX” and matches nothing else in the column, so I excluded it from the category chart. Permit type runs to 78 distinct values. Type and category carry the analysis instead.

Findings

Permits by City (Bar Chart)

Two places account for over half the county’s permits. Ellicott City logs 16,430 and Columbia 15,467 across the three years. Laurel is next at 6,625, then Elkridge at 4,464, and everything below drops into the low thousands. Columbia is the county’s largest population center and Ellicott City is the county seat, so the concentration is not surprising. Whether it is proportional, this file cannot show. It carries no population figures.

city_count <- data.frame(count(permits_full, CITY))
city_count <- city_count[order(city_count$n, decreasing = TRUE),]
city_count$CITY <- str_to_title(city_count$CITY)

ggplot(city_count[1:10,], aes(x = reorder(CITY, -n), y = n)) +
  geom_bar(stat = "identity", fill = "steelblue", colour = "black") +
  labs(title = "Building Permits Issued by City (Top 10)",
       x = "City",
       y = "Permit Count",
       caption = "Source: opendata.howardcountymd.gov (kvz2-j5cj) | 2022, 2024-2025 (Jan-Nov)") +
  scale_y_continuous(labels = comma, limits = c(0, 19000)) +
  theme_light() +
  theme(plot.title = element_text(hjust = 0.5),
        axis.text.x = element_text(angle = 45, hjust = 1)) +
  geom_text(aes(label = scales::comma(n)), vjust = -0.5, size = 3.5)

Permits by Type and Year (Stacked Bar Chart)

Electrical dominates at 20,381 permits, then plumbing at 14,601 and building at 12,848. The bottom three types together account for under 10%. The year bands sit close to even inside each bar. Volume is stable across the three years rather than growing or declining, and that stability holds within each trade rather than only in total.

type_year <- permits_full %>%
  select(IssueDate, TYPE) %>%
  mutate(Year = year(IssueDate)) %>%
  group_by(TYPE, Year) %>%
  summarize(n = length(TYPE), .groups = "keep") %>%
  data.frame()

type_year$Year <- as.factor(type_year$Year)
type_year$TYPE <- str_to_title(type_year$TYPE)

type_tot <- type_year %>%
  select(TYPE, n) %>%
  group_by(TYPE) %>%
  summarize(tot = sum(n), .groups = "keep") %>%
  data.frame()

ggplot(type_year, aes(x = reorder(TYPE, n, sum), y = n, fill = Year)) +
  geom_bar(stat = "identity", position = position_stack(reverse = TRUE)) +
  coord_flip() +
  labs(title = "Permits by Type and Year",
       x = "",
       y = "Permit Count",
       fill = "Year",
       caption = "Source: opendata.howardcountymd.gov (kvz2-j5cj) | 2022, 2024-2025 (Jan-Nov)") +
  theme_light() +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_fill_brewer(palette = "RdPu",
                    guide = guide_legend(reverse = TRUE)) +
  geom_text(data = type_tot,
            aes(x = TYPE, y = tot,
                label = scales::comma(tot), fill = NULL),
            hjust = -0.2, size = 4) +
  scale_y_continuous(labels = comma,
                     limits = c(0, 28000),
                     breaks = seq(0, 28000, 10000))

Monthly Permit Volume (Line Chart)

Permitting is seasonal, and all three years follow the same curve. Volume bottoms in January and February, climbs through spring, holds through summer and falls in November. The years are not identical. 2025 runs higher from July onward, and 2022 is the most volatile, dropping sharply in September before recovering. The file shows the pattern but not its cause. Weather, staffing and construction cycles would all produce this shape.

monthly_yr <- permits_full %>%
  select(IssueDate) %>%
  mutate(Year = year(IssueDate),
         Month = month(IssueDate)) %>%
  group_by(Year, Month) %>%
  summarize(n = length(IssueDate), .groups = "keep") %>%
  data.frame()

monthly_yr$Year <- as.factor(monthly_yr$Year)

month_labels <- c("Jan","Feb","Mar","Apr","May","Jun",
                  "Jul","Aug","Sep","Oct","Nov")

ggplot(monthly_yr, aes(x = Month, y = n, group = Year)) +
  geom_line(aes(color = Year), linewidth = 1.2) +
  geom_point(shape = 21, size = 3, color = "black", fill = "white") +
  labs(title = "Monthly Permit Volume by Year",
       x = "Month",
       y = "Permit Count",
       color = "Year",
       caption = "2023 is missing: this dataset contains no records between Dec 2022 and Nov 2023.
Source: opendata.howardcountymd.gov (kvz2-j5cj) | 2022, 2024-2025 (Jan-Nov)") +
  scale_x_continuous(breaks = 1:11,
                     labels = month_labels,
                     minor_breaks = NULL) +
  scale_y_continuous(labels = comma, limits = c(0, 2200)) +
  theme_light() +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_color_brewer(palette = "Dark2",
                     guide = guide_legend(reverse = TRUE))

Permits by Type and Month (Heat Map)

Electrical, HVAC and building peak in different months. Electrical peaks in July at 2,154 against 1,508 in January, a swing of 43%. HVAC peaks in July and August, which tracks cooling season. Building peaks in April, well ahead of both. Grading and sign stay between 30 and 95 permits a month all year. The seasonal effect sits almost entirely in the three highest-volume trades.

my_months <- c("Jan","Feb","Mar","Apr","May","Jun",
               "Jul","Aug","Sep","Oct","Nov")

type_month <- permits_full %>%
  select(IssueDate, TYPE) %>%
  mutate(Month = months(IssueDate, abbreviate = TRUE)) %>%
  group_by(TYPE, Month) %>%
  summarize(n = length(TYPE), .groups = "keep") %>%
  data.frame()

type_month$TYPE <- str_to_title(type_month$TYPE)

type_month$Month <- factor(type_month$Month, levels = my_months)
type_month$TYPE  <- factor(type_month$TYPE,
                           levels = c("Grading", "Sign", "Fire Protection",
                                      "Hvac", "Building", "Plumbing",
                                      "Electrical"))

breaks <- seq(0, max(type_month$n), by = 250)

ggplot(type_month, aes(x = Month, y = TYPE, fill = n)) +
  geom_tile(color = "black") +
  geom_text(aes(label = comma(n)), size = 3) +
  labs(title = "Permits by Type and Month",
       x = "Month",
       y = "Permit Type",
       fill = "Permit Count",
       caption = "Source: opendata.howardcountymd.gov (kvz2-j5cj) | 2022, 2024-2025 (Jan-Nov)") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_fill_continuous(low = "white", high = "red", breaks = breaks) +
  guides(fill = guide_legend(reverse = TRUE,
                             override.aes = list(color = "black")))

Permit Category Share (Pie Chart)

Howard County permitting is residential, with commercial work at 16% a distant second and signage barely registering. The residential share holds between 80% and 85% across all three years, so composition is stable even as volume shifts. Permit counts do not reflect project size or value, so commercial work may represent a larger share of economic activity than the counts suggest.

cat_df <- permits_full %>%
  filter(CATEGORY != "MX") %>%
  select(CATEGORY) %>%
  group_by(CATEGORY) %>%
  summarize(n = length(CATEGORY), .groups = "keep") %>%
  ungroup() %>%
  mutate(PctOfTotal = round(100 * n / sum(n), 1)) %>%
  data.frame()

cat_df$CATEGORY <- str_to_title(cat_df$CATEGORY)
cat_df$CATEGORY <- factor(cat_df$CATEGORY,
                          levels = c("Residential", "Commercial", "Sign"))

ggplot(data = cat_df, aes(x = "", y = n, fill = CATEGORY)) +
  geom_bar(stat = "identity", position = "fill") +
  coord_polar(theta = "y", start = 0) +
  labs(fill = "Category",
       x = NULL,
       y = NULL,
       title = "Permit Category Share",
       caption = "Source: opendata.howardcountymd.gov (kvz2-j5cj) | 2022, 2024-2025 (Jan-Nov)") +
  theme_light() +
  theme(plot.title = element_text(hjust = 0.5),
        axis.text = element_blank(),
        axis.ticks = element_blank(),
        panel.grid = element_blank()) +
  scale_fill_brewer(palette = "Blues", direction = -1) +
  geom_text(aes(x = 1.7, label = paste0(PctOfTotal, "%")),
            size = 4,
            position = position_fill(vjust = 0.5))

Conclusion

Howard County permitting is residential and seasonal. The more useful finding is about the data itself. Twelve months of records are absent, and census tract fails independently from December 2024 forward. Anyone using this file for trend analysis would need to find both before trusting a single year-over-year comparison.