Pawan.
  • About
  • Experience
  • Content Hub
  • Skills
  • Editor
  • R Code
  • Contact

Contents

  • Overview
  • 1 · All India Domestic GST Trends
  • 2 · GST Collection by State
  • 3 · Major States — Collection Trends
  • 4 · GST Composition — 2025-26
  • 5 · Heatmap — State × Year
  • 6 · YoY Growth Rate — All India
  • 7 · CGST vs SGST — Selected States
  • 8 · CESS Contribution Trend
  • 9 · Statistical Summary Tables
    • Top 15 States by Average GST Collection
    • Top 10 Growth Leaders (2023-24 → 2024-25)
  • Session Info
  • Report an issue

India GST Collection Analysis

  • Show All Code
  • Hide All Code

  • View Source

Interactive Dashboard — Financial Years 2017–18 to 2024–25

Author

GST Analytics

Published

April 13, 2026

Modified

April 14, 2026

Show code
gst_data <- fread("/home/pawan/tax/gst.csv")

num_cols <- c("CGST", "SGST", "IGST", "CESS", "TOTAL")
gst_data[, (num_cols) := lapply(.SD, function(x) as.numeric(gsub(",", "", x))),
         .SDcols = num_cols]

latest_year  <- gst_data[!is.na(TOTAL) & TOTAL > 0, max(Year)]
exclude_rows <- c("Domestic GST Collection- All India",
                  "Gross Revenue",
                  "IGST and CESS on Import - All India")

Overview

This interactive report explores India’s Goods and Services Tax (GST) collection data across states and financial years. All monetary values are expressed in ₹ Thousands Crores (K Cr).

Tip: Hover over any chart element to see detailed figures. Use the toolbar icon to download charts as PNG.


1 · All India Domestic GST Trends

Year-wise breakdown of Central (CGST), State (SGST), Integrated (IGST) and Compensation Cess (CESS) for the consolidated All India basket.

Show code
all_india_long <- melt(
  gst_data[State == "Domestic GST Collection- All India"],
  id.vars       = c("State", "Year"),
  measure.vars  = c("CGST", "SGST", "IGST", "CESS"),
  variable.name = "Tax_Type",
  value.name    = "Amount"
)
all_india_long[, tooltip := paste0(
  "<b>", Tax_Type, "</b><br>Year: ", Year,
  "<br>Amount: ", fmt_cr(Amount)
)]

p1 <- ggplot(all_india_long,
             aes(x = Year, y = Amount / 1000,
                 fill = Tax_Type, group = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity",
    aes(tooltip = tooltip, data_id = paste(Year, Tax_Type)),
    width = 0.7, alpha = 0.88
  ) +
  scale_fill_manual(values = gst_colors) +
  scale_y_continuous(labels = label_comma(suffix = " K")) +
  labs(title    = "All India Domestic GST Collection Trends",
       subtitle = "Year-wise breakdown by tax type  (₹ Thousands Crores)",
       x = "Financial Year", y = "Amount (₹ Thousands Crores)",
       fill = "Tax Type",
       caption = "Source: GST Council / Ministry of Finance") +
  theme_gst()

girafe(ggobj = p1, options = girafe_opts("#3B9EE0"))

Stacked bars — hover to inspect individual tax components.


2 · GST Collection by State

Top 16 states ranked by cumulative GST, broken down by tax type.

Show code
states_dt   <- gst_data[!State %in% exclude_rows]
top16_names <- states_dt[TOTAL > 0,
                          .(S = sum(TOTAL, na.rm = TRUE)), by = State
                         ][order(-S)][1:16, State]

states_long <- melt(
  states_dt[State %in% top16_names],
  id.vars       = c("State", "Year", "TOTAL"),
  measure.vars  = c("CGST", "SGST", "IGST", "CESS"),
  variable.name = "Tax_Type",
  value.name    = "Amount"
)
states_long[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year,
  "<br><i>", Tax_Type, "</i>: ", fmt_cr(Amount),
  "<br>Total: ", fmt_cr(TOTAL)
)]

p2 <- ggplot(states_long,
             aes(x = Year, y = Amount / 1000, fill = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity",
    aes(tooltip = tooltip, data_id = paste(State, Year, Tax_Type)),
    alpha = 0.88
  ) +
  scale_fill_manual(values = gst_colors) +
  scale_y_continuous(labels = label_comma()) +
  facet_wrap(~State, scales = "free_y", ncol = 4) +
  labs(title    = "GST Collection by State",
       subtitle = "Top 16 states — stacked by tax type  (₹ Thousands Crores)",
       x = "Financial Year", y = "Amount (₹ Thousands Crores)",
       fill = "Tax Type", caption = "Source: GST Council") +
  theme_gst() +
  theme(axis.text.x = element_text(size = 7))

girafe(ggobj = p2, width_svg = 14, height_svg = 10,
       options = girafe_opts("#2DBE8E"))

Stacked bars per state — hover to see component amounts.


3 · Major States — Collection Trends

Line + point chart for 10 major states. Hover a point to see YoY growth.

Show code
major_states <- c("Maharashtra", "Gujarat", "Tamil Nadu", "Karnataka",
                  "Uttar Pradesh", "West Bengal", "Delhi",
                  "Rajasthan", "Madhya Pradesh", "Telangana")

state_palette <- c(
  Maharashtra = "#3B9EE0", Gujarat = "#2DBE8E",
  `Tamil Nadu` = "#F5A623", Karnataka = "#E05C5C",
  `Uttar Pradesh` = "#9B6CF5", `West Bengal` = "#F0C060",
  Delhi = "#60C0F0", Rajasthan = "#E08050",
  `Madhya Pradesh` = "#70D080", Telangana = "#C070D0"
)

major_dt <- gst_data[State %in% major_states & !is.na(TOTAL) & TOTAL > 0
                     ][order(State, Year)]
major_dt[, Growth := round((TOTAL - shift(TOTAL)) / shift(TOTAL) * 100, 1),
         by = State]
major_dt[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year,
  "<br>Total: ", fmt_cr(TOTAL),
  ifelse(!is.na(Growth), paste0("<br>YoY: <b>", Growth, "%</b>"), "")
)]

p3 <- ggplot(major_dt,
             aes(x = Year, y = TOTAL / 1000,
                 color = State, group = State)) +
  geom_line_interactive(linewidth = 1.3,
                        aes(tooltip = State, data_id = State)) +
  geom_point_interactive(size = 2.5,
                         aes(tooltip = tooltip,
                             data_id = paste(State, Year))) +
  scale_color_manual(values = state_palette) +
  scale_y_continuous(labels = label_comma()) +
  facet_wrap(~State, ncol = 5, scales = "free_y") +
  labs(title    = "GST Collection Trends — Major States",
       subtitle = "Hover a point to see YoY growth  (₹ Thousands Crores)",
       x = "Financial Year", y = "Total GST (₹ Thousands Crores)",
       caption  = "Source: GST Council") +
  theme_gst() +
  theme(legend.position = "none")

girafe(ggobj = p3, width_svg = 14, height_svg = 7,
       options = girafe_opts("#F5A623"))

Each panel is one state; hover points reveal YoY growth %.


4 · GST Composition — 2025-26

Donut showing the share of each tax head for the latest available financial year across all states.

Show code
latest_comp <- gst_data[!State %in% exclude_rows & Year == latest_year,
                          .(CGST = sum(CGST, na.rm = TRUE),
                            SGST = sum(SGST, na.rm = TRUE),
                            IGST = sum(IGST, na.rm = TRUE),
                            CESS = sum(CESS, na.rm = TRUE))]

comp_long <- melt(latest_comp,
                  measure.vars  = c("CGST", "SGST", "IGST", "CESS"),
                  variable.name = "Tax_Type",
                  value.name    = "Amount")
comp_long[, Share := round(Amount / sum(Amount) * 100, 1)]
comp_long[, tooltip := paste0(
  "<b>", Tax_Type, "</b><br>",
  fmt_cr(Amount), "<br>Share: <b>", Share, "%</b>"
)]

p4 <- ggplot(comp_long,
             aes(x = "", y = Amount / 1000, fill = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity", width = 1,
    aes(tooltip = tooltip, data_id = as.character(Tax_Type)),
    color = "#0F1923", linewidth = 1.2
  ) +
  coord_polar("y", start = 0) +
  geom_text(aes(label = paste0(Tax_Type, "\n", Share, "%")),
            position = position_stack(vjust = 0.55),
            color = "white", size = 4.2, fontface = "bold") +
  scale_fill_manual(values = gst_colors) +
  labs(title    = paste0("GST Composition — All States (", latest_year, ")"),
       subtitle = "Distribution across tax types",
       fill = "Tax Type", caption = "Source: GST Council") +
  theme_gst() +
  theme(axis.title = element_blank(), axis.text = element_blank(),
        panel.grid = element_blank())

girafe(ggobj = p4, options = girafe_opts("#9B6CF5"))

Hover a segment to see the exact amount and share.


5 · Heatmap — State × Year

Show code
heatmap_dt <- gst_data[
  !State %in% c(exclude_rows, "OIDAR", "Other Territory") & TOTAL > 0,
  .(State, Year, TOTAL)
]
top20 <- heatmap_dt[, .(S = sum(TOTAL, na.rm = TRUE)), by = State
                    ][order(-S)][1:20, State]

heatmap_dt <- heatmap_dt[State %in% top20]
heatmap_dt[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year, "<br>Total: ", fmt_cr(TOTAL)
)]

p5 <- ggplot(heatmap_dt,
             aes(x = Year,
                 y = reorder(State, TOTAL, FUN = sum),
                 fill = TOTAL / 1000)) +
  geom_tile_interactive(
    aes(tooltip = tooltip, data_id = paste(State, Year)),
    color = "#0F1923", linewidth = 0.4
  ) +
  scale_fill_gradient2(
    low = "#0D2137", mid = "#1E6FAD", high = "#FFD166",
    midpoint = median(heatmap_dt$TOTAL / 1000, na.rm = TRUE),
    name = "₹ K Crores", labels = label_comma()
  ) +
  labs(title    = "Heatmap — GST Collections by State & Year",
       subtitle = "Top 20 States/UTs  (₹ Thousands Crores)",
       x = "Financial Year", y = NULL,
       caption  = "Source: GST Council") +
  theme_gst() +
  theme(legend.position = "right",
        axis.text.y = element_text(size = 9))

girafe(ggobj = p5, width_svg = 12, height_svg = 7,
       options = girafe_opts("#FFD166"))

Top 20 states by cumulative GST; brighter = higher collection.


6 · YoY Growth Rate — All India

Show code
growth_dt <- gst_data[
  State == "Domestic GST Collection- All India" & !is.na(TOTAL) & TOTAL > 0
][order(Year)]
growth_dt[, Growth_Rate := round((TOTAL - shift(TOTAL)) / shift(TOTAL) * 100, 1)]
growth_dt[, tooltip := paste0(
  "<b>", Year, "</b><br>Growth: <b>",
  ifelse(!is.na(Growth_Rate), paste0(Growth_Rate, "%"), "N/A"),
  "</b><br>Total: ", fmt_cr(TOTAL)
)]
growth_dt[, bar_fill := ifelse(Growth_Rate >= 0, "Positive", "Negative")]

p6 <- ggplot(growth_dt[!is.na(Growth_Rate)],
             aes(x = Year, y = Growth_Rate, fill = bar_fill)) +
  geom_bar_interactive(
    stat = "identity", width = 0.65, alpha = 0.88,
    aes(tooltip = tooltip, data_id = Year)
  ) +
  geom_hline(yintercept = 0, color = "#8FA8C8", linewidth = 0.6) +
  geom_text(
    aes(label = paste0(Growth_Rate, "%"),
        vjust = ifelse(Growth_Rate >= 0, -0.4, 1.4)),
    color = "#C8DCEF", size = 3.5, fontface = "bold"
  ) +
  scale_fill_manual(values = c(Positive = "#2DBE8E", Negative = "#E05C5C"),
                    guide = "none") +
  scale_y_continuous(labels = label_percent(scale = 1)) +
  labs(title    = "All India Domestic GST — YoY Growth Rate",
       subtitle = "Percentage change from previous financial year",
       x = "Financial Year", y = "Growth Rate (%)",
       caption  = "Source: GST Council") +
  theme_gst()

girafe(ggobj = p6, options = girafe_opts("#2DBE8E"))

Green = positive growth; red = contraction.


7 · CGST vs SGST — Selected States

Show code
comp_data  <- gst_data[State %in% c("Maharashtra", "Gujarat",
                                     "Tamil Nadu", "Karnataka") &
                          Year >= "2019-20"]
comp_long2 <- melt(comp_data,
                   id.vars       = c("State", "Year"),
                   measure.vars  = c("CGST", "SGST"),
                   variable.name = "Tax_Type",
                   value.name    = "Amount")
comp_long2[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year,
  "<br>", Tax_Type, ": ", fmt_cr(Amount)
)]

p7 <- ggplot(comp_long2,
             aes(x = Year, y = Amount / 1000, fill = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity", position = "dodge", width = 0.7, alpha = 0.88,
    aes(tooltip = tooltip, data_id = paste(State, Year, Tax_Type))
  ) +
  scale_fill_manual(values = c(CGST = "#3B9EE0", SGST = "#2DBE8E")) +
  scale_y_continuous(labels = label_comma()) +
  facet_wrap(~State, scales = "free_y") +
  labs(title    = "CGST vs SGST — Major States",
       subtitle = "Central vs State GST since 2019-20  (₹ Thousands Crores)",
       x = "Financial Year", y = "Amount (₹ Thousands Crores)",
       fill = "Tax Type", caption = "Source: GST Council") +
  theme_gst()

girafe(ggobj = p7, width_svg = 11, height_svg = 7,
       options = girafe_opts("#3B9EE0"))

Dodged bars — CGST and SGST compared side by side.


8 · CESS Contribution Trend

Show code
cess_dt <- gst_data[!State %in% exclude_rows & TOTAL > 0,
                     .(CESS_Share = round(
                       sum(CESS, na.rm = TRUE) /
                         sum(TOTAL, na.rm = TRUE) * 100, 2)),
                     by = Year][order(Year)]
cess_dt[, tooltip := paste0(
  "<b>", Year, "</b><br>CESS Share: <b>", CESS_Share, "%</b>"
)]

p8 <- ggplot(cess_dt, aes(x = Year, y = CESS_Share, group = 1)) +
  geom_line_interactive(
    color = "#F5A623", linewidth = 1.4,
    aes(tooltip = "CESS trend", data_id = "line")
  ) +
  geom_point_interactive(
    color = "#E05C5C", size = 4, alpha = 0.9,
    aes(tooltip = tooltip, data_id = Year)
  ) +
  geom_text(aes(label = paste0(CESS_Share, "%")),
            vjust = -1, color = "#C8DCEF", size = 3.5) +
  scale_y_continuous(
    labels = label_percent(scale = 1),
    limits = c(0, max(cess_dt$CESS_Share, na.rm = TRUE) * 1.3)
  ) +
  labs(title    = "CESS Contribution to Total GST",
       subtitle = "Percentage share of CESS across all states, by year",
       x = "Financial Year", y = "CESS Share (%)",
       caption  = "Source: GST Council") +
  theme_gst()

girafe(ggobj = p8, options = girafe_opts("#F5A623"))

Hover points to see exact CESS share per year.


9 · Statistical Summary Tables

Top 15 States by Average GST Collection

Show code
state_avg <- gst_data[
  !State %in% c(exclude_rows, "OIDAR", "Other Territory") & TOTAL > 0,
  .(
    `Avg (₹ K Cr)` = round(mean(TOTAL, na.rm = TRUE) / 1000, 2),
    `Max (₹ K Cr)` = round(max(TOTAL,  na.rm = TRUE) / 1000, 2),
    `Min (₹ K Cr)` = round(min(TOTAL,  na.rm = TRUE) / 1000, 2)
  ),
  by = State
][order(-`Avg (₹ K Cr)`)][1:15]

knitr::kable(state_avg, format.args = list(big.mark = ","))
State Avg (₹ K Cr) Max (₹ K Cr) Min (₹ K Cr)
Maharashtra 235.99 359.86 105.19
Karnataka 106.03 159.56 48.14
Gujarat 96.57 136.75 45.91
Tamil Nadu 91.22 131.12 45.32
Haryana 77.04 119.36 36.81
Uttar Pradesh 76.61 112.21 36.86
Delhi 51.85 77.00 26.44
West Bengal 49.02 66.89 23.33
Telangana 45.42 62.99 21.35
Odisha 39.68 60.93 14.85
Rajasthan 39.07 54.78 18.46
Andhra Pradesh 32.62 44.83 14.33
Madhya Pradesh 31.85 45.07 15.54
Chhattisgarh 27.27 37.18 13.01
Jharkhand 26.62 36.84 14.16

Top 10 Growth Leaders (2023-24 → 2024-25)

Show code
growth_by_state <- gst_data[
  !State %in% c(exclude_rows, "OIDAR", "Other Territory") &
    Year %in% c("2023-24", "2024-25") & TOTAL > 0
]
growth_wide <- dcast(growth_by_state, State ~ Year, value.var = "TOTAL")
growth_wide[, `Growth (%)` := round((`2024-25` - `2023-24`) / `2023-24` * 100, 2)]

knitr::kable(
  growth_wide[order(-`Growth (%)`)][1:10,
    .(State, `2023-24 (₹ Cr)` = `2023-24`,
             `2024-25 (₹ Cr)` = `2024-25`,
             `Growth (%)`)],
  format.args = list(big.mark = ",")
)
State 2023-24 (₹ Cr) 2024-25 (₹ Cr) Growth (%)
Haryana 102,914 119,362 15.98
Delhi 66,445 77,002 15.89
Ladakh 481 546 13.51
Tripura 1,053 1,193 13.30
Andaman and Nicobar Island 428 482 12.62
Maharashtra 320,117 359,855 12.41
Bihar 18,021 20,208 12.14
Assam 15,602 17,415 11.62
Odisha 54,748 60,928 11.29
Punjab 24,061 26,721 11.06

Session Info

R session info
sessionInfo()
#> R version 4.5.3 (2026-03-11)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Manjaro Linux
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/libblas.so.3.12.0 
#> LAPACK: /usr/lib/liblapack.so.3.12.0  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8    LC_NUMERIC=C            LC_TIME=en_IN          
#>  [4] LC_COLLATE=en_US.UTF-8  LC_MONETARY=en_IN       LC_MESSAGES=en_US.UTF-8
#>  [7] LC_PAPER=en_IN          LC_NAME=C               LC_ADDRESS=C           
#> [10] LC_TELEPHONE=C          LC_MEASUREMENT=en_IN    LC_IDENTIFICATION=C    
#> 
#> time zone: Asia/Kolkata
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] scales_1.4.0        ggiraph_0.9.2       ggplot2_4.0.1      
#> [4] data.table_1.18.2.1
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6            jsonlite_2.0.0          dplyr_1.1.4            
#>  [4] compiler_4.5.3          tidyselect_1.2.1        Rcpp_1.1.1             
#>  [7] dichromat_2.0-0.1       fontquiver_0.2.1        systemfonts_1.3.1      
#> [10] yaml_2.3.12             fastmap_1.2.0           R6_2.6.1               
#> [13] labeling_0.4.3          gdtools_0.4.4           generics_0.1.4         
#> [16] knitr_1.50              htmlwidgets_1.6.4       tibble_3.3.1           
#> [19] pillar_1.11.1           RColorBrewer_1.1-3      rlang_1.2.0            
#> [22] xfun_0.55               S7_0.2.1                cli_3.6.6              
#> [25] withr_3.0.2             magrittr_2.0.5          digest_0.6.39          
#> [28] grid_4.5.3              lifecycle_1.0.5         vctrs_0.7.3            
#> [31] evaluate_1.0.5          glue_1.8.0              farver_2.1.2           
#> [34] fontLiberation_0.1.0    fontBitstreamVera_0.1.1 purrr_1.2.0            
#> [37] rmarkdown_2.30          tools_4.5.3             pkgconfig_2.0.3        
#> [40] htmltools_0.5.9
Source Code
---
title: "India GST Collection Analysis"
subtitle: "Interactive Dashboard — Financial Years 2017–18 to 2024–25"
author: "GST Analytics"
date: "2026-04-13"
date-modified: last-modified

format:
  html:
    theme:
      - cosmo
      - custom.scss          # optional — drop if not using Sass overrides
    toc: true
    toc-depth: 3
    toc-title: "Contents"
    toc-location: left
    number-sections: false
    smooth-scroll: true
    code-fold: true
    code-tools: true
    code-summary: "Show code"
    fig-width: 11
    fig-height: 6
    fig-align: center
    embed-resources: true    # single self-contained HTML file
    page-layout: full
    df-print: paged

execute:
  echo: true
  warning: false
  message: false
  cache: false

knitr:
  opts_chunk:
    comment: "#>"
---

```{r setup}
#| label: setup
#| include: false

## ── Libraries ────────────────────────────────────────────────
library(data.table)
library(ggplot2)
library(ggiraph)
library(scales)

## ── Custom theme ─────────────────────────────────────────────
theme_gst <- function(base_size = 13) {
  theme_minimal(base_size = base_size) %+replace%
    theme(
      plot.background  = element_rect(fill = "#0F1923", color = NA),
      panel.background = element_rect(fill = "#131F2E", color = NA),
      panel.grid.major = element_line(color = "#1E3048", linewidth = 0.4),
      panel.grid.minor = element_blank(),
      panel.border     = element_blank(),
      plot.title       = element_text(color = "#F0F4F8", size = base_size + 4,
                                      face = "bold", hjust = 0,
                                      margin = margin(b = 4)),
      plot.subtitle    = element_text(color = "#8FA8C8", size = base_size - 1,
                                      hjust = 0, margin = margin(b = 12)),
      plot.caption     = element_text(color = "#4A6584", size = 9, hjust = 1),
      axis.title       = element_text(color = "#8FA8C8", size = base_size - 1),
      axis.text        = element_text(color = "#7A9AB8", size = base_size - 2),
      axis.text.x      = element_text(angle = 45, hjust = 1),
      strip.text       = element_text(color = "#C8DCEF", size = base_size - 1,
                                      face = "bold"),
      strip.background = element_rect(fill = "#1A2D44", color = NA),
      legend.background = element_rect(fill = "#0F1923", color = NA),
      legend.key        = element_rect(fill = "#0F1923", color = NA),
      legend.text       = element_text(color = "#8FA8C8", size = base_size - 2),
      legend.title      = element_text(color = "#C8DCEF", size = base_size - 1,
                                       face = "bold"),
      legend.position   = "top",
      plot.margin       = margin(16, 16, 12, 16)
    )
}

gst_colors <- c(
  CGST  = "#3B9EE0",
  SGST  = "#2DBE8E",
  IGST  = "#F5A623",
  CESS  = "#E05C5C",
  TOTAL = "#9B6CF5"
)

## ── Shared girafe options ────────────────────────────────────
girafe_opts <- function(border_color = "#3B9EE0") {
  list(
    opts_hover(css = "opacity:1;stroke:white;stroke-width:1.5px;"),
    opts_hover_inv(css = "opacity:0.2;"),
    opts_tooltip(css = paste0(
      "background:#1A2D44;color:#F0F4F8;",
      "border:1px solid ", border_color, ";border-radius:6px;",
      "padding:8px 12px;font-size:13px;font-family:monospace;"
    )),
    opts_toolbar(saveaspng = TRUE),
    opts_sizing(rescale = TRUE)
  )
}

fmt_cr <- function(x) {
  paste0("₹", formatC(round(x / 1000, 1), format = "f",
                       digits = 1, big.mark = ","), " K Cr")
}
```

```{r load-data}
#| label: load-data
#| cache: true

gst_data <- fread("/home/pawan/tax/gst.csv")

num_cols <- c("CGST", "SGST", "IGST", "CESS", "TOTAL")
gst_data[, (num_cols) := lapply(.SD, function(x) as.numeric(gsub(",", "", x))),
         .SDcols = num_cols]

latest_year  <- gst_data[!is.na(TOTAL) & TOTAL > 0, max(Year)]
exclude_rows <- c("Domestic GST Collection- All India",
                  "Gross Revenue",
                  "IGST and CESS on Import - All India")
```

## Overview

This interactive report explores **India's Goods and Services Tax (GST)** collection data across states and financial years. All monetary values are expressed in **₹ Thousands Crores (K Cr)**.

> **Tip:** Hover over any chart element to see detailed figures. Use the toolbar icon to download charts as PNG.

---

## 1 · All India Domestic GST Trends {#sec-all-india}

Year-wise breakdown of Central (CGST), State (SGST), Integrated (IGST) and Compensation Cess (CESS) for the consolidated All India basket.

```{r plot1-all-india}
#| label: plot1-all-india
#| fig-cap: "Stacked bars — hover to inspect individual tax components."

all_india_long <- melt(
  gst_data[State == "Domestic GST Collection- All India"],
  id.vars       = c("State", "Year"),
  measure.vars  = c("CGST", "SGST", "IGST", "CESS"),
  variable.name = "Tax_Type",
  value.name    = "Amount"
)
all_india_long[, tooltip := paste0(
  "<b>", Tax_Type, "</b><br>Year: ", Year,
  "<br>Amount: ", fmt_cr(Amount)
)]

p1 <- ggplot(all_india_long,
             aes(x = Year, y = Amount / 1000,
                 fill = Tax_Type, group = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity",
    aes(tooltip = tooltip, data_id = paste(Year, Tax_Type)),
    width = 0.7, alpha = 0.88
  ) +
  scale_fill_manual(values = gst_colors) +
  scale_y_continuous(labels = label_comma(suffix = " K")) +
  labs(title    = "All India Domestic GST Collection Trends",
       subtitle = "Year-wise breakdown by tax type  (₹ Thousands Crores)",
       x = "Financial Year", y = "Amount (₹ Thousands Crores)",
       fill = "Tax Type",
       caption = "Source: GST Council / Ministry of Finance") +
  theme_gst()

girafe(ggobj = p1, options = girafe_opts("#3B9EE0"))
```

---

## 2 · GST Collection by State {#sec-states}

Top 16 states ranked by cumulative GST, broken down by tax type.

```{r plot2-states}
#| label: plot2-states
#| fig-height: 10
#| fig-cap: "Stacked bars per state — hover to see component amounts."

states_dt   <- gst_data[!State %in% exclude_rows]
top16_names <- states_dt[TOTAL > 0,
                          .(S = sum(TOTAL, na.rm = TRUE)), by = State
                         ][order(-S)][1:16, State]

states_long <- melt(
  states_dt[State %in% top16_names],
  id.vars       = c("State", "Year", "TOTAL"),
  measure.vars  = c("CGST", "SGST", "IGST", "CESS"),
  variable.name = "Tax_Type",
  value.name    = "Amount"
)
states_long[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year,
  "<br><i>", Tax_Type, "</i>: ", fmt_cr(Amount),
  "<br>Total: ", fmt_cr(TOTAL)
)]

p2 <- ggplot(states_long,
             aes(x = Year, y = Amount / 1000, fill = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity",
    aes(tooltip = tooltip, data_id = paste(State, Year, Tax_Type)),
    alpha = 0.88
  ) +
  scale_fill_manual(values = gst_colors) +
  scale_y_continuous(labels = label_comma()) +
  facet_wrap(~State, scales = "free_y", ncol = 4) +
  labs(title    = "GST Collection by State",
       subtitle = "Top 16 states — stacked by tax type  (₹ Thousands Crores)",
       x = "Financial Year", y = "Amount (₹ Thousands Crores)",
       fill = "Tax Type", caption = "Source: GST Council") +
  theme_gst() +
  theme(axis.text.x = element_text(size = 7))

girafe(ggobj = p2, width_svg = 14, height_svg = 10,
       options = girafe_opts("#2DBE8E"))
```

---

## 3 · Major States — Collection Trends {#sec-major}

Line + point chart for 10 major states. Hover a point to see YoY growth.

```{r plot3-major-states}
#| label: plot3-major-states
#| fig-height: 7
#| fig-cap: "Each panel is one state; hover points reveal YoY growth %."

major_states <- c("Maharashtra", "Gujarat", "Tamil Nadu", "Karnataka",
                  "Uttar Pradesh", "West Bengal", "Delhi",
                  "Rajasthan", "Madhya Pradesh", "Telangana")

state_palette <- c(
  Maharashtra = "#3B9EE0", Gujarat = "#2DBE8E",
  `Tamil Nadu` = "#F5A623", Karnataka = "#E05C5C",
  `Uttar Pradesh` = "#9B6CF5", `West Bengal` = "#F0C060",
  Delhi = "#60C0F0", Rajasthan = "#E08050",
  `Madhya Pradesh` = "#70D080", Telangana = "#C070D0"
)

major_dt <- gst_data[State %in% major_states & !is.na(TOTAL) & TOTAL > 0
                     ][order(State, Year)]
major_dt[, Growth := round((TOTAL - shift(TOTAL)) / shift(TOTAL) * 100, 1),
         by = State]
major_dt[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year,
  "<br>Total: ", fmt_cr(TOTAL),
  ifelse(!is.na(Growth), paste0("<br>YoY: <b>", Growth, "%</b>"), "")
)]

p3 <- ggplot(major_dt,
             aes(x = Year, y = TOTAL / 1000,
                 color = State, group = State)) +
  geom_line_interactive(linewidth = 1.3,
                        aes(tooltip = State, data_id = State)) +
  geom_point_interactive(size = 2.5,
                         aes(tooltip = tooltip,
                             data_id = paste(State, Year))) +
  scale_color_manual(values = state_palette) +
  scale_y_continuous(labels = label_comma()) +
  facet_wrap(~State, ncol = 5, scales = "free_y") +
  labs(title    = "GST Collection Trends — Major States",
       subtitle = "Hover a point to see YoY growth  (₹ Thousands Crores)",
       x = "Financial Year", y = "Total GST (₹ Thousands Crores)",
       caption  = "Source: GST Council") +
  theme_gst() +
  theme(legend.position = "none")

girafe(ggobj = p3, width_svg = 14, height_svg = 7,
       options = girafe_opts("#F5A623"))
```

---

## 4 · GST Composition — `r latest_year` {#sec-composition}

Donut showing the share of each tax head for the latest available financial year across all states.

```{r plot4-composition}
#| label: plot4-composition
#| fig-height: 5
#| fig-cap: "Hover a segment to see the exact amount and share."

latest_comp <- gst_data[!State %in% exclude_rows & Year == latest_year,
                          .(CGST = sum(CGST, na.rm = TRUE),
                            SGST = sum(SGST, na.rm = TRUE),
                            IGST = sum(IGST, na.rm = TRUE),
                            CESS = sum(CESS, na.rm = TRUE))]

comp_long <- melt(latest_comp,
                  measure.vars  = c("CGST", "SGST", "IGST", "CESS"),
                  variable.name = "Tax_Type",
                  value.name    = "Amount")
comp_long[, Share := round(Amount / sum(Amount) * 100, 1)]
comp_long[, tooltip := paste0(
  "<b>", Tax_Type, "</b><br>",
  fmt_cr(Amount), "<br>Share: <b>", Share, "%</b>"
)]

p4 <- ggplot(comp_long,
             aes(x = "", y = Amount / 1000, fill = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity", width = 1,
    aes(tooltip = tooltip, data_id = as.character(Tax_Type)),
    color = "#0F1923", linewidth = 1.2
  ) +
  coord_polar("y", start = 0) +
  geom_text(aes(label = paste0(Tax_Type, "\n", Share, "%")),
            position = position_stack(vjust = 0.55),
            color = "white", size = 4.2, fontface = "bold") +
  scale_fill_manual(values = gst_colors) +
  labs(title    = paste0("GST Composition — All States (", latest_year, ")"),
       subtitle = "Distribution across tax types",
       fill = "Tax Type", caption = "Source: GST Council") +
  theme_gst() +
  theme(axis.title = element_blank(), axis.text = element_blank(),
        panel.grid = element_blank())

girafe(ggobj = p4, options = girafe_opts("#9B6CF5"))
```

---

## 5 · Heatmap — State × Year {#sec-heatmap}

```{r plot5-heatmap}
#| label: plot5-heatmap
#| fig-height: 7
#| fig-cap: "Top 20 states by cumulative GST; brighter = higher collection."

heatmap_dt <- gst_data[
  !State %in% c(exclude_rows, "OIDAR", "Other Territory") & TOTAL > 0,
  .(State, Year, TOTAL)
]
top20 <- heatmap_dt[, .(S = sum(TOTAL, na.rm = TRUE)), by = State
                    ][order(-S)][1:20, State]

heatmap_dt <- heatmap_dt[State %in% top20]
heatmap_dt[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year, "<br>Total: ", fmt_cr(TOTAL)
)]

p5 <- ggplot(heatmap_dt,
             aes(x = Year,
                 y = reorder(State, TOTAL, FUN = sum),
                 fill = TOTAL / 1000)) +
  geom_tile_interactive(
    aes(tooltip = tooltip, data_id = paste(State, Year)),
    color = "#0F1923", linewidth = 0.4
  ) +
  scale_fill_gradient2(
    low = "#0D2137", mid = "#1E6FAD", high = "#FFD166",
    midpoint = median(heatmap_dt$TOTAL / 1000, na.rm = TRUE),
    name = "₹ K Crores", labels = label_comma()
  ) +
  labs(title    = "Heatmap — GST Collections by State & Year",
       subtitle = "Top 20 States/UTs  (₹ Thousands Crores)",
       x = "Financial Year", y = NULL,
       caption  = "Source: GST Council") +
  theme_gst() +
  theme(legend.position = "right",
        axis.text.y = element_text(size = 9))

girafe(ggobj = p5, width_svg = 12, height_svg = 7,
       options = girafe_opts("#FFD166"))
```

---

## 6 · YoY Growth Rate — All India {#sec-growth}

```{r plot6-growth}
#| label: plot6-growth
#| fig-cap: "Green = positive growth; red = contraction."

growth_dt <- gst_data[
  State == "Domestic GST Collection- All India" & !is.na(TOTAL) & TOTAL > 0
][order(Year)]
growth_dt[, Growth_Rate := round((TOTAL - shift(TOTAL)) / shift(TOTAL) * 100, 1)]
growth_dt[, tooltip := paste0(
  "<b>", Year, "</b><br>Growth: <b>",
  ifelse(!is.na(Growth_Rate), paste0(Growth_Rate, "%"), "N/A"),
  "</b><br>Total: ", fmt_cr(TOTAL)
)]
growth_dt[, bar_fill := ifelse(Growth_Rate >= 0, "Positive", "Negative")]

p6 <- ggplot(growth_dt[!is.na(Growth_Rate)],
             aes(x = Year, y = Growth_Rate, fill = bar_fill)) +
  geom_bar_interactive(
    stat = "identity", width = 0.65, alpha = 0.88,
    aes(tooltip = tooltip, data_id = Year)
  ) +
  geom_hline(yintercept = 0, color = "#8FA8C8", linewidth = 0.6) +
  geom_text(
    aes(label = paste0(Growth_Rate, "%"),
        vjust = ifelse(Growth_Rate >= 0, -0.4, 1.4)),
    color = "#C8DCEF", size = 3.5, fontface = "bold"
  ) +
  scale_fill_manual(values = c(Positive = "#2DBE8E", Negative = "#E05C5C"),
                    guide = "none") +
  scale_y_continuous(labels = label_percent(scale = 1)) +
  labs(title    = "All India Domestic GST — YoY Growth Rate",
       subtitle = "Percentage change from previous financial year",
       x = "Financial Year", y = "Growth Rate (%)",
       caption  = "Source: GST Council") +
  theme_gst()

girafe(ggobj = p6, options = girafe_opts("#2DBE8E"))
```

---

## 7 · CGST vs SGST — Selected States {#sec-cgst-sgst}

```{r plot7-cgst-sgst}
#| label: plot7-cgst-sgst
#| fig-height: 7
#| fig-cap: "Dodged bars — CGST and SGST compared side by side."

comp_data  <- gst_data[State %in% c("Maharashtra", "Gujarat",
                                     "Tamil Nadu", "Karnataka") &
                          Year >= "2019-20"]
comp_long2 <- melt(comp_data,
                   id.vars       = c("State", "Year"),
                   measure.vars  = c("CGST", "SGST"),
                   variable.name = "Tax_Type",
                   value.name    = "Amount")
comp_long2[, tooltip := paste0(
  "<b>", State, "</b><br>Year: ", Year,
  "<br>", Tax_Type, ": ", fmt_cr(Amount)
)]

p7 <- ggplot(comp_long2,
             aes(x = Year, y = Amount / 1000, fill = Tax_Type)) +
  geom_bar_interactive(
    stat = "identity", position = "dodge", width = 0.7, alpha = 0.88,
    aes(tooltip = tooltip, data_id = paste(State, Year, Tax_Type))
  ) +
  scale_fill_manual(values = c(CGST = "#3B9EE0", SGST = "#2DBE8E")) +
  scale_y_continuous(labels = label_comma()) +
  facet_wrap(~State, scales = "free_y") +
  labs(title    = "CGST vs SGST — Major States",
       subtitle = "Central vs State GST since 2019-20  (₹ Thousands Crores)",
       x = "Financial Year", y = "Amount (₹ Thousands Crores)",
       fill = "Tax Type", caption = "Source: GST Council") +
  theme_gst()

girafe(ggobj = p7, width_svg = 11, height_svg = 7,
       options = girafe_opts("#3B9EE0"))
```

---

## 8 · CESS Contribution Trend {#sec-cess}

```{r plot8-cess}
#| label: plot8-cess
#| fig-cap: "Hover points to see exact CESS share per year."

cess_dt <- gst_data[!State %in% exclude_rows & TOTAL > 0,
                     .(CESS_Share = round(
                       sum(CESS, na.rm = TRUE) /
                         sum(TOTAL, na.rm = TRUE) * 100, 2)),
                     by = Year][order(Year)]
cess_dt[, tooltip := paste0(
  "<b>", Year, "</b><br>CESS Share: <b>", CESS_Share, "%</b>"
)]

p8 <- ggplot(cess_dt, aes(x = Year, y = CESS_Share, group = 1)) +
  geom_line_interactive(
    color = "#F5A623", linewidth = 1.4,
    aes(tooltip = "CESS trend", data_id = "line")
  ) +
  geom_point_interactive(
    color = "#E05C5C", size = 4, alpha = 0.9,
    aes(tooltip = tooltip, data_id = Year)
  ) +
  geom_text(aes(label = paste0(CESS_Share, "%")),
            vjust = -1, color = "#C8DCEF", size = 3.5) +
  scale_y_continuous(
    labels = label_percent(scale = 1),
    limits = c(0, max(cess_dt$CESS_Share, na.rm = TRUE) * 1.3)
  ) +
  labs(title    = "CESS Contribution to Total GST",
       subtitle = "Percentage share of CESS across all states, by year",
       x = "Financial Year", y = "CESS Share (%)",
       caption  = "Source: GST Council") +
  theme_gst()

girafe(ggobj = p8, options = girafe_opts("#F5A623"))
```

---

## 9 · Statistical Summary Tables {#sec-tables}

### Top 15 States by Average GST Collection

```{r table-avg}
#| label: table-avg

state_avg <- gst_data[
  !State %in% c(exclude_rows, "OIDAR", "Other Territory") & TOTAL > 0,
  .(
    `Avg (₹ K Cr)` = round(mean(TOTAL, na.rm = TRUE) / 1000, 2),
    `Max (₹ K Cr)` = round(max(TOTAL,  na.rm = TRUE) / 1000, 2),
    `Min (₹ K Cr)` = round(min(TOTAL,  na.rm = TRUE) / 1000, 2)
  ),
  by = State
][order(-`Avg (₹ K Cr)`)][1:15]

knitr::kable(state_avg, format.args = list(big.mark = ","))
```

### Top 10 Growth Leaders (2023-24 → 2024-25)

```{r table-growth}
#| label: table-growth

growth_by_state <- gst_data[
  !State %in% c(exclude_rows, "OIDAR", "Other Territory") &
    Year %in% c("2023-24", "2024-25") & TOTAL > 0
]
growth_wide <- dcast(growth_by_state, State ~ Year, value.var = "TOTAL")
growth_wide[, `Growth (%)` := round((`2024-25` - `2023-24`) / `2023-24` * 100, 2)]

knitr::kable(
  growth_wide[order(-`Growth (%)`)][1:10,
    .(State, `2023-24 (₹ Cr)` = `2023-24`,
             `2024-25 (₹ Cr)` = `2024-25`,
             `Growth (%)`)],
  format.args = list(big.mark = ",")
)
```

---

## Session Info {#sec-session}

```{r session}
#| label: session
#| code-fold: true
#| code-summary: "R session info"
sessionInfo()
```

© Pawan

Built with Quarto & R

  • Report an issue