Last active
February 8, 2026 21:36
-
-
Save tarekahsan709/064a6bdd8100197e35b43b0587b7c7d1 to your computer and use it in GitHub Desktop.
Meta Add library spent plot of Bangladesh
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
| """ | |
| Facebook Ad Library Analysis: BNP vs Jamaat-e-Islami spending strategy. | |
| Reads the Meta Ad Library CSV for Bangladesh (last 7 days ending Feb 4, 2026), | |
| classifies pages by party affiliation, and produces a dot-strip chart | |
| comparing how each party distributes its ad budget. | |
| """ | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from matplotlib.lines import Line2D | |
| from pathlib import Path | |
| # --------------------------------------------------------------------------- | |
| # Config | |
| # --------------------------------------------------------------------------- | |
| DATA_DIR = Path("/your_data_path/FacebookAdLibraryReport_2026-02-04_BD_last_7_days") | |
| CSV_PATH = DATA_DIR / "FacebookAdLibraryReport_2026-02-04_BD_last_7_days_advertisers.csv" | |
| OUTPUT_PNG = DATA_DIR / "ad_strategy.png" | |
| OUTPUT_PDF = DATA_DIR / "ad_strategy.pdf" | |
| COLORS = { | |
| "bnp_division": "#3b82f6", | |
| "bnp_other": "#93c5fd", | |
| "jamaat_news": "#ea580c", | |
| "jamaat_leader": "#f59e0b", | |
| "jamaat_party": "#fed7aa", | |
| } | |
| # Page IDs verified through ad content review and public reporting (TBS News). | |
| BNP_PAGE_IDS = { | |
| # Division media cells | |
| 101489482880980, # Sylhet Division | |
| 108829088795372, # Barishal Division | |
| 113809844959141, # Rangpur Division | |
| 110235991986292, # Chittagong Division | |
| 100998746257599, # Rajshahi Division | |
| 103192059367184, # Khulna Division | |
| 114137871592874, # Mymensingh Division | |
| 105862165769952, # Comilla Division | |
| 114868274857729, # Faridpur Division | |
| 110377721977993, # Dhaka Division | |
| # Central / official | |
| 266326536843123, # BNP Media Cell (central) | |
| 353130515348425, # Bangladesh Nationalist Party-BNP | |
| 48637583555, # Tarique Rahman | |
| 1067017226489655, # Chattogram 11 | |
| # Campaign pages | |
| 436519556222527, # "Shobar Age Bangladesh" (BNP slogan) | |
| 632905399898625, # "Amar Dhan Amar Pran" (BNP paddy symbol) | |
| # Bengali-only named pages | |
| 103034728693441, # Dhanmondi Thana BNP | |
| 975452825646784, # "We are BNP" | |
| 796283883756020, # Sitakunda Municipality BNP | |
| 640180602509888, # "Amra BNP Poribar" | |
| } | |
| JAMAAT_PAGE_IDS = { | |
| # Official | |
| 646141765463753, # Bangladesh Jamaat-e-Islami | |
| # Leader / campaign | |
| 1655241494703764, # Dr. Shafiqur Rahman (Ameer) | |
| 246495106022463, # Dr. Shafiqur Rahman supporter group | |
| 890218124171294, # "Cholo Eksathe Gori Bangladesh" campaign page | |
| # News pages (verified via ad content review) | |
| 480337488505055, # Dhaka News 24 Online | |
| 872833149257837, # Dhaka 17 News | |
| # District / local | |
| 659361743938323, # Meherpur District | |
| 988383787685953, # Health Division | |
| 550885641431340, # Fulbaria Upazila | |
| 390203917517769, # Mirzapur Upazila | |
| 377170452151660, # Kalmakanda | |
| 100328701372847, # Bhola-3 | |
| 109150173882307, # Narayanganj City | |
| 560690680469651, # Mainuddin Ahmad | |
| 242917998894979, # Dr. Md. Helal Uddin | |
| 138774682912184, # Nurul Islam Bulbul | |
| 882367374957490, # "Why Jamaat?" | |
| 107392944247304, # Chattogram City | |
| 579634365236995, # Kishoreganj District | |
| 483259565025899, # Dr. Md. Shafiqul Islam Masud | |
| 103950325070228, # Charfasson Upazila | |
| 545028055353662, # Sher-e-Bangla Nagor | |
| 974839482373673, # Noakhali District | |
| 995392000320962, # "Adhunik Jamat 24" | |
| 801793316357319, # Youth & Sports Division | |
| } | |
| JAMAAT_NEWS_PAGE_IDS = {480337488505055, 872833149257837} | |
| JAMAAT_LEADER_PAGE_IDS = {1655241494703764, 246495106022463, 890218124171294} | |
| BNP_DIVISION_PATTERN = r"BNP Media Cell -|BNP Sylhet" | |
| # --------------------------------------------------------------------------- | |
| # Data loading | |
| # --------------------------------------------------------------------------- | |
| def load_ad_data(path: Path) -> pd.DataFrame: | |
| ads = pd.read_csv(path) | |
| ads["Amount spent (USD)"] = ( | |
| ads["Amount spent (USD)"] | |
| .replace("≤100", "50") | |
| .pipe(pd.to_numeric) | |
| ) | |
| return ads | |
| # --------------------------------------------------------------------------- | |
| # Classification | |
| # --------------------------------------------------------------------------- | |
| def filter_bnp(ads: pd.DataFrame) -> pd.DataFrame: | |
| by_id = ads["Page ID"].isin(BNP_PAGE_IDS) | |
| by_name = ads["Page name"].str.contains("BNP", case=False, na=False) | |
| by_disclaimer = ads["Disclaimer"].str.contains( | |
| "BNP|Bangladesh Nationalist Party", case=False, na=False | |
| ) | |
| is_bnpp = ads["Page name"].str.contains("BNPP", case=False, na=False) | |
| return ads[(by_id | by_name | by_disclaimer) & ~is_bnpp].copy() | |
| def filter_jamaat(ads: pd.DataFrame) -> pd.DataFrame: | |
| by_id = ads["Page ID"].isin(JAMAAT_PAGE_IDS) | |
| by_name = ads["Page name"].str.contains("Jamaat", case=False, na=False) | |
| by_disclaimer = ads["Disclaimer"].str.contains("Jamaat", case=False, na=False) | |
| return ads[by_id | by_name | by_disclaimer].copy() | |
| def classify_jamaat_page(page_id: int) -> str: | |
| if page_id in JAMAAT_NEWS_PAGE_IDS: | |
| return "news" | |
| if page_id in JAMAAT_LEADER_PAGE_IDS: | |
| return "leader" | |
| return "party" | |
| # --------------------------------------------------------------------------- | |
| # Plotting | |
| # --------------------------------------------------------------------------- | |
| def dot_size(spend: float) -> float: | |
| return spend * 0.08 + 30 | |
| def build_chart(bnp: pd.DataFrame, jamaat: pd.DataFrame) -> plt.Figure: | |
| plt.rcParams.update({ | |
| "font.size": 10, | |
| "font.family": "sans-serif", | |
| "figure.facecolor": "white", | |
| }) | |
| bnp_spend = bnp["Amount spent (USD)"].sum() | |
| jamaat_spend = jamaat["Amount spent (USD)"].sum() | |
| bnp_sorted = bnp.sort_values("Amount spent (USD)", ascending=False).reset_index(drop=True) | |
| jamaat_sorted = jamaat.sort_values("Amount spent (USD)", ascending=False).reset_index(drop=True) | |
| fig, ax = plt.subplots(figsize=(11, 5)) | |
| y_bnp, y_jamaat = 1.0, 0.0 | |
| # BNP dots | |
| bnp_division_ids = set( | |
| bnp[bnp["Page name"].str.contains(BNP_DIVISION_PATTERN, na=False)]["Page ID"] | |
| ) | |
| for i, row in bnp_sorted.iterrows(): | |
| color = COLORS["bnp_division"] if row["Page ID"] in bnp_division_ids else COLORS["bnp_other"] | |
| ax.scatter( | |
| i, y_bnp, | |
| s=dot_size(row["Amount spent (USD)"]), | |
| c=color, edgecolors="white", linewidth=0.4, alpha=0.85, zorder=3, | |
| ) | |
| # Jamaat dots | |
| for i, row in jamaat_sorted.iterrows(): | |
| category = classify_jamaat_page(row["Page ID"]) | |
| color = COLORS[f"jamaat_{category}"] | |
| ax.scatter( | |
| i, y_jamaat, | |
| s=dot_size(row["Amount spent (USD)"]), | |
| c=color, edgecolors="white", linewidth=0.4, alpha=0.85, zorder=3, | |
| ) | |
| # Label the two dominant Jamaat news pages | |
| news_spends = jamaat[jamaat["Page ID"].isin(JAMAAT_NEWS_PAGE_IDS)].sort_values( | |
| "Amount spent (USD)", ascending=False | |
| ) | |
| news_label = " + ".join( | |
| f'{row["Page name"]} (${row["Amount spent (USD)"]:,.0f})' | |
| for _, row in news_spends.iterrows() | |
| ) | |
| ax.text( | |
| 0.5, y_jamaat - 0.28, | |
| f"Two largest: {news_label}", | |
| ha="left", fontsize=7.5, color="#666666", | |
| ) | |
| # Axes | |
| ax.set_yticks([y_jamaat, y_bnp]) | |
| ax.set_yticklabels( | |
| [ | |
| f"Jamaat-e-Islami\n${jamaat_spend:,.0f} · {len(jamaat)} pages", | |
| f"BNP\n${bnp_spend:,.0f} · {len(bnp)} pages", | |
| ], | |
| fontsize=11, fontweight="bold", | |
| ) | |
| ax.tick_params(axis="y", length=0) | |
| ax.set_ylim(-0.5, 1.5) | |
| ax.set_xticks([]) | |
| ax.set_xlabel( | |
| "Each dot = one Facebook page, sorted by spend (largest to smallest)", | |
| fontsize=8.5, color="#888888", | |
| ) | |
| for spine in ax.spines.values(): | |
| spine.set_visible(False) | |
| # Legend | |
| legend_items = [ | |
| Line2D([0], [0], marker="o", color="w", markerfacecolor=COLORS["bnp_division"], markersize=9, label="BNP division cells"), | |
| Line2D([0], [0], marker="o", color="w", markerfacecolor=COLORS["bnp_other"], markersize=7, label="BNP other pages"), | |
| Line2D([0], [0], marker="o", color="w", markerfacecolor=COLORS["jamaat_news"], markersize=9, label="Jamaat news pages"), | |
| Line2D([0], [0], marker="o", color="w", markerfacecolor=COLORS["jamaat_leader"], markersize=8, label="Jamaat leader pages"), | |
| Line2D([0], [0], marker="o", color="w", markerfacecolor=COLORS["jamaat_party"], markersize=6, label="Jamaat party pages"), | |
| ] | |
| ax.legend( | |
| handles=legend_items, loc="upper right", | |
| fontsize=8, framealpha=0.95, borderpad=0.8, handletextpad=0.5, | |
| ) | |
| fig.text( | |
| 0.5, -0.03, | |
| "Source: Meta Ad Library, Bangladesh, 7 days ending Feb 4, 2026", | |
| ha="center", fontsize=7.5, color="#aaaaaa", fontstyle="italic", | |
| ) | |
| plt.tight_layout() | |
| return fig | |
| # --------------------------------------------------------------------------- | |
| # Main | |
| # --------------------------------------------------------------------------- | |
| def main(): | |
| ads = load_ad_data(CSV_PATH) | |
| bnp = filter_bnp(ads) | |
| jamaat = filter_jamaat(ads) | |
| bnp_spend = bnp["Amount spent (USD)"].sum() | |
| jamaat_spend = jamaat["Amount spent (USD)"].sum() | |
| print(f"BNP: ${bnp_spend:,.0f} ({len(bnp)} pages) | Jamaat: ${jamaat_spend:,.0f} ({len(jamaat)} pages)") | |
| fig = build_chart(bnp, jamaat) | |
| fig.savefig(OUTPUT_PNG, dpi=150, bbox_inches="tight", facecolor="white") | |
| fig.savefig(OUTPUT_PDF, bbox_inches="tight", facecolor="white") | |
| print(f"Saved: {OUTPUT_PNG.name} and {OUTPUT_PDF.name}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment