Created
April 8, 2026 09:16
-
-
Save akionsight/2c06ff61577d4f4a3438c4fe7e8beafc to your computer and use it in GitHub Desktop.
Generates Charts for AQI data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| AQI April 2025 — Knowledge Park III, Greater Noida | |
| Generates 3 chart PNGs from aqi.csv (place both files in the same folder). | |
| Requirements: | |
| pip install matplotlib pandas | |
| """ | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import matplotlib.colors as mcolors | |
| from matplotlib.patches import Patch | |
| import warnings, os | |
| warnings.filterwarnings("ignore") | |
| # ── Load data ────────────────────────────────────────────────────────────────── | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| CSV = os.path.join(HERE, "aqi.csv") | |
| df = pd.read_csv(CSV) | |
| hours = [c for c in df.columns if c != "Date"] | |
| pivot = df.set_index("Date")[hours].copy() | |
| pivot.columns = range(24) | |
| daily_avg = pivot.mean(axis=1) | |
| daily_max = pivot.max(axis=1) | |
| daily_min = pivot.min(axis=1) | |
| hourly_avg = pivot.mean(axis=0) | |
| days = pivot.index.tolist() | |
| day_labels = [f"Apr {d}" for d in days] | |
| PALETTE = ["#4a9e5c", "#d4c84a", "#e8913a", "#c0392b"] | |
| BOUNDS = [0, 50, 100, 200, 300] | |
| CMAP = mcolors.ListedColormap(PALETTE) | |
| NORM = mcolors.BoundaryNorm(BOUNDS, CMAP.N) | |
| BG = "#f9f9f7" | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CHART 1 — HEATMAP | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| fig1, ax = plt.subplots(figsize=(18, 9), facecolor=BG) | |
| fig1.suptitle( | |
| "Hourly AQI Heatmap — April 2025 | Knowledge Park III, Greater Noida", | |
| fontsize=13, fontweight="bold", color="#2c2c2c", y=1.01, | |
| ) | |
| data = pivot.values.astype(float) | |
| im = ax.imshow(data, aspect="auto", cmap=CMAP, norm=NORM, interpolation="nearest") | |
| for r, day in enumerate(days): | |
| for c in range(24): | |
| val = data[r, c] | |
| if not np.isnan(val): | |
| fc = "white" if val > 130 else "#333" | |
| ax.text(c, r, str(int(val)), ha="center", va="center", | |
| fontsize=5.5, color=fc, fontweight="500") | |
| ax.set_xticks(range(24)) | |
| ax.set_xticklabels([str(h) for h in range(24)], fontsize=8) | |
| ax.set_yticks(range(len(days))) | |
| ax.set_yticklabels(day_labels, fontsize=7.5) | |
| ax.set_xlabel("Hour of day", fontsize=10, labelpad=5) | |
| ax.set_ylabel("Date", fontsize=10, labelpad=5) | |
| ax.set_title("Each cell = one hour reading. Blank cells = missing data.", | |
| fontsize=9, color="#666", pad=6) | |
| cbar = fig1.colorbar(im, ax=ax, orientation="vertical", | |
| fraction=0.018, pad=0.01, shrink=0.95) | |
| cbar.set_ticks([25, 75, 150, 250]) | |
| cbar.set_ticklabels(["Good", "Satisfactory", "Moderate", "Poor"], fontsize=8) | |
| legend_patches = [ | |
| Patch(facecolor="#4a9e5c", label="Good (0–50)"), | |
| Patch(facecolor="#d4c84a", label="Satisfactory (51–100)"), | |
| Patch(facecolor="#e8913a", label="Moderate (101–200)"), | |
| ] | |
| ax.legend(handles=legend_patches, loc="lower right", fontsize=8, | |
| framealpha=0.9, bbox_to_anchor=(1.17, 0)) | |
| fig1.tight_layout() | |
| out1 = os.path.join(HERE, "chart1_heatmap.png") | |
| fig1.savefig(out1, dpi=180, bbox_inches="tight", facecolor=BG) | |
| print(f"Saved {out1}") | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CHART 2 — DAILY TREND | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| fig2, ax = plt.subplots(figsize=(14, 6), facecolor=BG) | |
| fig2.suptitle( | |
| "Daily AQI Trend — April 2025 | Knowledge Park III, Greater Noida", | |
| fontsize=13, fontweight="bold", color="#2c2c2c", y=1.01, | |
| ) | |
| x = np.array(days) | |
| ax.fill_between(x, daily_min, daily_max, | |
| color="#e8913a", alpha=0.15, label="Min–Max range") | |
| ax.plot(x, daily_max, color="#c0392b", linewidth=1.4, linestyle="--", | |
| marker="o", markersize=3.5, label="Daily max") | |
| ax.plot(x, daily_avg, color="#e8913a", linewidth=2, | |
| marker="o", markersize=4, label="Daily avg", zorder=5) | |
| ax.plot(x, daily_min, color="#4a9e5c", linewidth=1.4, linestyle="--", | |
| marker="o", markersize=3.5, label="Daily min") | |
| for level, label, clr in [ | |
| (50, "Good / Satisfactory", "#4a9e5c"), | |
| (100, "Satisfactory / Moderate", "#d4aa00"), | |
| (200, "Moderate / Poor", "#c0392b"), | |
| ]: | |
| ax.axhline(level, color=clr, linewidth=0.8, linestyle=":", alpha=0.7) | |
| ax.text(30.6, level + 1.5, label, fontsize=7, color=clr, va="bottom") | |
| best_day = daily_avg.idxmin() | |
| worst_day = daily_avg.idxmax() | |
| ax.annotate( | |
| f"Best\nApr {best_day} ({daily_avg[best_day]:.0f})", | |
| xy=(best_day, daily_avg[best_day]), | |
| xytext=(best_day + 1.8, daily_avg[best_day] - 16), | |
| fontsize=8, color="#4a9e5c", fontweight="bold", | |
| arrowprops=dict(arrowstyle="->", color="#4a9e5c", lw=1), | |
| ) | |
| ax.annotate( | |
| f"Worst\nApr {worst_day} ({daily_avg[worst_day]:.0f})", | |
| xy=(worst_day, daily_avg[worst_day]), | |
| xytext=(worst_day + 1.8, daily_avg[worst_day] + 4), | |
| fontsize=8, color="#c0392b", fontweight="bold", | |
| arrowprops=dict(arrowstyle="->", color="#c0392b", lw=1), | |
| ) | |
| ax.set_xlim(0.5, 31.5) | |
| ax.set_ylim(40, 190) | |
| ax.set_xticks(range(1, 31)) | |
| ax.set_xticklabels(day_labels, rotation=45, ha="right", fontsize=8) | |
| ax.set_ylabel("AQI", fontsize=10) | |
| ax.set_title("Shaded band = daily min–max range; line = daily average", | |
| fontsize=9, color="#666", pad=6) | |
| ax.legend(fontsize=8.5, loc="upper left", framealpha=0.9) | |
| ax.grid(axis="y", alpha=0.25, linewidth=0.7) | |
| ax.set_facecolor("#fafaf8") | |
| fig2.tight_layout() | |
| out2 = os.path.join(HERE, "chart2_daily_trend.png") | |
| fig2.savefig(out2, dpi=180, bbox_inches="tight", facecolor=BG) | |
| print(f"Saved {out2}") | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CHART 3 — HOURLY PATTERN | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| fig3, ax = plt.subplots(figsize=(9, 9), facecolor=BG) | |
| fig3.suptitle( | |
| "Average AQI by Hour of Day — April 2025 | Knowledge Park III, Greater Noida", | |
| fontsize=12, fontweight="bold", color="#2c2c2c", y=1.01, | |
| ) | |
| bar_colors = ["#d4c84a" if v <= 100 else "#e8913a" for v in hourly_avg] | |
| bars = ax.barh(range(24), hourly_avg.values, | |
| color=bar_colors, edgecolor="none", height=0.65) | |
| for i, (bar, val) in enumerate(zip(bars, hourly_avg.values)): | |
| ax.text(val + 0.1, i, f"{val:.1f}", va="center", fontsize=8, color="#555") | |
| ax.set_yticks(range(24)) | |
| ax.set_yticklabels([f"{h}:00" for h in range(24)], fontsize=8.5) | |
| ax.set_xlabel("Average AQI (across all 30 days)", fontsize=10) | |
| ax.set_title( | |
| "The near-flat pattern indicates pollution is episode-driven\n" | |
| "(weather / regional transport), not tied to time of day.", | |
| fontsize=9, color="#666", pad=6, | |
| ) | |
| ax.set_xlim(100, 120) | |
| ax.invert_yaxis() | |
| ax.grid(axis="x", alpha=0.25, linewidth=0.7) | |
| ax.set_facecolor("#fafaf8") | |
| fig3.tight_layout() | |
| out3 = os.path.join(HERE, "chart3_hourly_pattern.png") | |
| fig3.savefig(out3, dpi=180, bbox_inches="tight", facecolor=BG) | |
| print(f"Saved {out3}") | |
| print("\nDone — 3 charts generated.") | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import matplotlib.colors as mcolors | |
| from matplotlib.patches import Patch | |
| import warnings, os | |
| warnings.filterwarnings("ignore") | |
| # ── Load data ────────────────────────────────────────────────────────────────── | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| CSV = os.path.join(HERE, "aqi_chandni.csv") | |
| df = pd.read_csv(CSV, na_values="NA") | |
| hours = [c for c in df.columns if c != "Date"] | |
| pivot = df.set_index("Date")[hours].copy() | |
| pivot.columns = range(24) | |
| daily_avg = pivot.mean(axis=1) | |
| daily_max = pivot.max(axis=1) | |
| daily_min = pivot.min(axis=1) | |
| hourly_avg = pivot.mean(axis=0) | |
| days = pivot.index.tolist() | |
| day_labels = [f"Apr {d}" for d in days] | |
| # AQI colour scale — extended to cover Poor (201-300) since data reaches 287 | |
| PALETTE = ["#4a9e5c", "#d4c84a", "#e8913a", "#c0392b", "#7b241c"] | |
| BOUNDS = [0, 50, 100, 200, 300, 400] | |
| CMAP = mcolors.ListedColormap(PALETTE) | |
| NORM = mcolors.BoundaryNorm(BOUNDS, CMAP.N) | |
| BG = "#f9f9f7" | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CHART 1 — HEATMAP | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| fig1, ax = plt.subplots(figsize=(18, 9), facecolor=BG) | |
| fig1.suptitle( | |
| "Hourly AQI Heatmap — April 2025 | Chandni Chowk, Delhi (IITM)", | |
| fontsize=13, fontweight="bold", color="#2c2c2c", y=1.01, | |
| ) | |
| data = pivot.values.astype(float) | |
| im = ax.imshow(data, aspect="auto", cmap=CMAP, norm=NORM, interpolation="nearest") | |
| for r, day in enumerate(days): | |
| for c in range(24): | |
| val = data[r, c] | |
| if not np.isnan(val): | |
| fc = "white" if val > 150 else "#333" | |
| ax.text(c, r, str(int(val)), ha="center", va="center", | |
| fontsize=5.5, color=fc, fontweight="500") | |
| ax.set_xticks(range(24)) | |
| ax.set_xticklabels([str(h) for h in range(24)], fontsize=8) | |
| ax.set_yticks(range(len(days))) | |
| ax.set_yticklabels(day_labels, fontsize=7.5) | |
| ax.set_xlabel("Hour of day", fontsize=10, labelpad=5) | |
| ax.set_ylabel("Date", fontsize=10, labelpad=5) | |
| ax.set_title("Each cell = one hour reading. Grey = missing data.", | |
| fontsize=9, color="#666", pad=6) | |
| cbar = fig1.colorbar(im, ax=ax, orientation="vertical", | |
| fraction=0.018, pad=0.01, shrink=0.95) | |
| cbar.set_ticks([25, 75, 150, 250, 350]) | |
| cbar.set_ticklabels(["Good", "Satisfactory", "Moderate", "Poor", "Very Poor"], | |
| fontsize=8) | |
| legend_patches = [ | |
| Patch(facecolor="#4a9e5c", label="Good (0–50)"), | |
| Patch(facecolor="#d4c84a", label="Satisfactory (51–100)"), | |
| Patch(facecolor="#e8913a", label="Moderate (101–200)"), | |
| Patch(facecolor="#c0392b", label="Poor (201–300)"), | |
| ] | |
| ax.legend(handles=legend_patches, loc="lower right", fontsize=8, | |
| framealpha=0.9, bbox_to_anchor=(1.17, 0)) | |
| fig1.tight_layout() | |
| out1 = os.path.join(HERE, "chandni_chart1_heatmap.png") | |
| fig1.savefig(out1, dpi=180, bbox_inches="tight", facecolor=BG) | |
| print(f"Saved {out1}") | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CHART 2 — DAILY TREND | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| fig2, ax = plt.subplots(figsize=(14, 6), facecolor=BG) | |
| fig2.suptitle( | |
| "Daily AQI Trend — April 2025 | Chandni Chowk, Delhi (IITM)", | |
| fontsize=13, fontweight="bold", color="#2c2c2c", y=1.01, | |
| ) | |
| x = np.array(days) | |
| ax.fill_between(x, daily_min, daily_max, | |
| color="#c0392b", alpha=0.12, label="Min–Max range") | |
| ax.plot(x, daily_max, color="#7b241c", linewidth=1.4, linestyle="--", | |
| marker="o", markersize=3.5, label="Daily max") | |
| ax.plot(x, daily_avg, color="#c0392b", linewidth=2, | |
| marker="o", markersize=4, label="Daily avg", zorder=5) | |
| ax.plot(x, daily_min, color="#e8913a", linewidth=1.4, linestyle="--", | |
| marker="o", markersize=3.5, label="Daily min") | |
| for level, label, clr in [ | |
| (100, "Satisfactory / Moderate", "#d4aa00"), | |
| (200, "Moderate / Poor", "#c0392b"), | |
| (300, "Poor / Very Poor", "#7b241c"), | |
| ]: | |
| ax.axhline(level, color=clr, linewidth=0.8, linestyle=":", alpha=0.7) | |
| ax.text(30.6, level + 2, label, fontsize=7, color=clr, va="bottom") | |
| valid_avg = daily_avg.dropna() | |
| best_day = valid_avg.idxmin() | |
| worst_day = valid_avg.idxmax() | |
| ax.annotate( | |
| f"Best\nApr {best_day} ({valid_avg[best_day]:.0f})", | |
| xy=(best_day, valid_avg[best_day]), | |
| xytext=(best_day + 1.8, valid_avg[best_day] - 22), | |
| fontsize=8, color="#e8913a", fontweight="bold", | |
| arrowprops=dict(arrowstyle="->", color="#e8913a", lw=1), | |
| ) | |
| ax.annotate( | |
| f"Worst\nApr {worst_day} ({valid_avg[worst_day]:.0f})", | |
| xy=(worst_day, valid_avg[worst_day]), | |
| xytext=(worst_day + 1.8, valid_avg[worst_day] + 5), | |
| fontsize=8, color="#7b241c", fontweight="bold", | |
| arrowprops=dict(arrowstyle="->", color="#7b241c", lw=1), | |
| ) | |
| ax.set_xlim(0.5, 31.5) | |
| ax.set_ylim(60, 310) | |
| ax.set_xticks(range(1, 31)) | |
| ax.set_xticklabels(day_labels, rotation=45, ha="right", fontsize=8) | |
| ax.set_ylabel("AQI", fontsize=10) | |
| ax.set_title("Shaded band = daily min–max range; line = daily average. Gaps = fully missing days.", | |
| fontsize=9, color="#666", pad=6) | |
| ax.legend(fontsize=8.5, loc="upper left", framealpha=0.9) | |
| ax.grid(axis="y", alpha=0.25, linewidth=0.7) | |
| ax.set_facecolor("#fafaf8") | |
| fig2.tight_layout() | |
| out2 = os.path.join(HERE, "chandni_chart2_daily_trend.png") | |
| fig2.savefig(out2, dpi=180, bbox_inches="tight", facecolor=BG) | |
| print(f"Saved {out2}") | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CHART 3 — HOURLY PATTERN | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| fig3, ax = plt.subplots(figsize=(9, 9), facecolor=BG) | |
| fig3.suptitle( | |
| "Average AQI by Hour of Day — April 2025 | Chandni Chowk, Delhi (IITM)", | |
| fontsize=12, fontweight="bold", color="#2c2c2c", y=1.01, | |
| ) | |
| def bar_color(v): | |
| if v <= 100: return "#d4c84a" | |
| if v <= 200: return "#e8913a" | |
| return "#c0392b" | |
| bar_colors = [bar_color(v) for v in hourly_avg] | |
| bars = ax.barh(range(24), hourly_avg.values, | |
| color=bar_colors, edgecolor="none", height=0.65) | |
| for i, (bar, val) in enumerate(zip(bars, hourly_avg.values)): | |
| ax.text(val + 0.5, i, f"{val:.1f}", va="center", fontsize=8, color="#555") | |
| ax.set_yticks(range(24)) | |
| ax.set_yticklabels([f"{h}:00" for h in range(24)], fontsize=8.5) | |
| ax.set_xlabel("Average AQI (across available days)", fontsize=10) | |
| ax.set_title( | |
| "Unlike Greater Noida, Chandni Chowk shows a diurnal rise —\n" | |
| "higher AQI in late evening / night hours.", | |
| fontsize=9, color="#666", pad=6, | |
| ) | |
| xmin = max(0, hourly_avg.min() - 15) | |
| xmax = hourly_avg.max() + 20 | |
| ax.set_xlim(xmin, xmax) | |
| for level, clr in [(100, "#d4aa00"), (200, "#c0392b")]: | |
| if xmin < level < xmax: | |
| ax.axvline(level, color=clr, linewidth=0.8, linestyle=":", alpha=0.8) | |
| ax.text(level + 1, 23.5, str(level), fontsize=7, color=clr) | |
| ax.invert_yaxis() | |
| ax.grid(axis="x", alpha=0.25, linewidth=0.7) | |
| ax.set_facecolor("#fafaf8") | |
| fig3.tight_layout() | |
| out3 = os.path.join(HERE, "chandni_chart3_hourly_pattern.png") | |
| fig3.savefig(out3, dpi=180, bbox_inches="tight", facecolor=BG) | |
| print(f"Saved {out3}") | |
| print("\nDone — 3 charts generated.") |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The data used is sourced from the Central Control Room for Air Quality Management