{"spec_id":"elbow-curve","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' elbow-curve: Elbow Curve for K-Means Clustering\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 90/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens -----------------------------------------------------------\nTHEME    <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG  <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK      <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\n\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# --- Data --------------------------------------------------------------------\n# Customer segmentation: K-means fit on annual spending / visit-frequency\n# features across k = 1..10, tracking within-cluster sum of squares (inertia).\nk_values <- 1:10\ninertia <- 5200 * exp(-0.55 * (k_values - 1)) + 180 + rnorm(10, mean = 0, sd = 15)\n\ndf <- tibble::tibble(k = k_values, inertia = inertia)\n\n# Kneedle-style elbow detection: point of maximum perpendicular distance\n# below the chord connecting the first and last (normalized) points.\nnorm_k <- (df$k - min(df$k)) / (max(df$k) - min(df$k))\nnorm_inertia <- (df$inertia - min(df$inertia)) / (max(df$inertia) - min(df$inertia))\nchord_y <- 1 - norm_k\nelbow_idx <- which.max(chord_y - norm_inertia)\nelbow_k <- df$k[elbow_idx]\nelbow_inertia <- df$inertia[elbow_idx]\n\n# Post-elbow plateau, shaded to sharpen the \"diminishing returns\" story.\ndf_plateau <- df[elbow_idx:nrow(df), ]\n\n# --- Plot ----------------------------------------------------------------\np <- ggplot(df, aes(x = k, y = inertia)) +\n  geom_ribbon(\n    data = df_plateau, aes(ymin = 0, ymax = inertia),\n    fill = IMPRINT_PALETTE[1], alpha = 0.12, color = NA\n  ) +\n  geom_vline(\n    xintercept = elbow_k, linetype = \"dashed\",\n    color = INK_SOFT, linewidth = 0.5, alpha = 0.6\n  ) +\n  geom_line(color = IMPRINT_PALETTE[1], linewidth = 1.1) +\n  geom_point(color = IMPRINT_PALETTE[1], size = 3.8) +\n  geom_point(\n    data = df[elbow_idx, ], shape = 21, size = 5.5,\n    fill = IMPRINT_PALETTE[1], color = INK, stroke = 0.8\n  ) +\n  annotate(\n    \"text\", x = elbow_k + 0.3, y = elbow_inertia + 380,\n    label = sprintf(\"Elbow: k = %d\", elbow_k),\n    color = INK, size = 3.4, hjust = 0\n  ) +\n  scale_x_continuous(breaks = k_values) +\n  scale_y_continuous(limits = c(0, NA)) +\n  labs(\n    title = \"elbow-curve · r · ggplot2 · anyplot.ai\",\n    x = \"Number of Clusters (k)\",\n    y = \"Inertia (Within-Cluster Sum of Squares)\"\n  ) +\n  theme_minimal(base_size = 8) +\n  theme(\n    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background  = element_rect(fill = PAGE_BG, color = NA),\n    panel.grid.major.y = element_line(color = INK, linewidth = 0.25),\n    panel.grid.major.x = element_blank(),\n    panel.grid.minor  = element_blank(),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.ticks        = element_blank(),\n    plot.title        = element_text(color = INK, size = 14)\n  )\n\n# --- Save ----------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}