imashutosh51

plotting

Aug 16th, 2025 (edited)
36
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 35.22 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. Unified tester script (fixed):
  4. - Saves all plots to ./plots/
  5. - Minor fixes: filename sanitization, avoid swarmplot warning, replace deprecated seaborn ci param,
  6. remove deprecated pandas infer_datetime_format usage.
  7. - Logic and functionality otherwise unchanged.
  8. """
  9.  
  10. import os
  11. from datetime import datetime
  12. import numpy as np
  13. import pandas as pd
  14. import matplotlib
  15. matplotlib.use("Agg") # headless backend so saving works on servers/CI
  16. import matplotlib.pyplot as plt
  17. import seaborn as sns
  18. import re
  19.  
  20. # ---------------- Helpers ----------------
  21. def _match_column(df, col_name):
  22. cols = df.columns
  23. cols_lower = [c.lower() for c in cols]
  24. clue = col_name.lower()
  25. if clue not in cols_lower:
  26. raise KeyError(f"Column '{col_name}' not found. Available: {list(df.columns)}")
  27. return cols[cols_lower.index(clue)]
  28.  
  29. def _sanitize_basename(name: str) -> str:
  30. """
  31. Replace problematic filesystem characters with underscore so filenames are portable.
  32. Keeps letters, numbers, dash, underscore; converts everything else to underscore.
  33. """
  34. # strip path and extension if present
  35. base = os.path.basename(os.path.splitext(name)[0])
  36. # replace any non [a-zA-Z0-9-_] with underscore
  37. return re.sub(r'[^A-Za-z0-9_\-]', '_', base)
  38.  
  39. def _timestamped_filename(base, out_dir="plots"):
  40. """
  41. Create output directory (out_dir) if missing and return a timestamped filename
  42. inside that directory using the pattern: <out_dir>/<sanitized_base>_2_<YYYYMMDD_HHMMSS>.png
  43. """
  44. os.makedirs(out_dir, exist_ok=True)
  45. ts = datetime.now().strftime("%Y%m%d_%H%M%S")
  46. base_name = _sanitize_basename(base)
  47. filename = f"{base_name}_2_{ts}.png"
  48. return os.path.join(out_dir, filename)
  49.  
  50. def plot_one_column_Universal(csv_file, col, chart_type, output_file="chart.png", figsize=(8,6), max_categories=30):
  51. """
  52. Universal single column plotting function that treats ALL data types neutrally.
  53. Simply counts occurrences of unique values regardless of data type.
  54.  
  55. Supported chart_types: bar, count, pie
  56. """
  57. try:
  58. print(f"[INFO] (Universal) Reading CSV: {csv_file}")
  59. try:
  60. df = pd.read_csv(csv_file, encoding='utf-8')
  61. print("[INFO] Read with encoding='utf-8'")
  62. except UnicodeDecodeError:
  63. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  64. print("[INFO] Read with encoding='ISO-8859-1'")
  65.  
  66. actual_col = _match_column(df, col)
  67. print(f"[INFO] Using column: '{actual_col}' (treating all data types neutrally)")
  68.  
  69. # Convert everything to string for neutral handling
  70. df[actual_col] = df[actual_col].astype("string").fillna("Missing")
  71.  
  72. # Handle high cardinality by collapsing to top N + "Other"
  73. n_unique = df[actual_col].nunique()
  74. print(f"[INFO] '{actual_col}' unique values: {n_unique}")
  75.  
  76. if n_unique > max_categories:
  77. print(f"[WARN] High cardinality ({n_unique}) — collapsing to top {max_categories} + 'Other'.")
  78. top_values = df[actual_col].value_counts().nlargest(max_categories).index
  79. df[actual_col] = df[actual_col].where(df[actual_col].isin(top_values), other="Other")
  80. print(f"[INFO] Values after collapsing: {df[actual_col].nunique()}")
  81.  
  82. # Get value counts
  83. counts = df[actual_col].value_counts().sort_values(ascending=False)
  84.  
  85. sns.set(style="whitegrid")
  86. plt.figure(figsize=figsize)
  87. ct = chart_type.lower()
  88.  
  89. if ct == "bar":
  90. sns.barplot(x=counts.index, y=counts.values)
  91. plt.title(f"Bar Plot: {actual_col} (Universal Count)")
  92. plt.xlabel(actual_col)
  93. plt.ylabel("Count")
  94. plt.xticks(rotation=45, ha="right")
  95.  
  96. elif ct == "count":
  97. sns.countplot(data=df, x=actual_col, order=counts.index)
  98. plt.title(f"Count Plot: {actual_col} (Universal Count)")
  99. plt.xticks(rotation=45, ha="right")
  100.  
  101. elif ct == "pie":
  102. plt.pie(counts.values, labels=counts.index, autopct='%1.1f%%', startangle=140)
  103. plt.title(f"Pie Chart: {actual_col} (Universal Count)")
  104.  
  105. else:
  106. raise ValueError(f"Unsupported chart type '{chart_type}'. Supported: bar, count, pie.")
  107.  
  108. plt.tight_layout()
  109. filename = _timestamped_filename(output_file)
  110. plt.savefig(filename)
  111. plt.close()
  112.  
  113. print(f"[SUCCESS] (Universal) Chart saved: {filename} (type: {chart_type})")
  114. return filename
  115.  
  116. except Exception as e:
  117. print(f"[ERROR] (Universal) {e}")
  118. return None
  119.  
  120. # ---------------- Plotting functions (robust) ----------------
  121. def plot_two_columns_NumericVsNumeric(csv_file, col_x, col_y, chart_type, output_file="chart.png", figsize=(8,6), bubble_size_col=None):
  122. try:
  123. print(f"[INFO] (NumericVsNumeric) Reading CSV: {csv_file}")
  124. try:
  125. df = pd.read_csv(csv_file, encoding='utf-8')
  126. print("[INFO] Read with encoding='utf-8'")
  127. except UnicodeDecodeError:
  128. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  129. print("[INFO] Read with encoding='ISO-8859-1'")
  130.  
  131. cols = df.columns
  132. cols_lower = [c.lower() for c in cols]
  133. x_l = col_x.lower()
  134. y_l = col_y.lower()
  135.  
  136. if x_l not in cols_lower or y_l not in cols_lower:
  137. raise KeyError(f"One or both columns not found. Available: {list(df.columns)}")
  138.  
  139. actual_x = cols[cols_lower.index(x_l)]
  140. actual_y = cols[cols_lower.index(y_l)]
  141. print(f"[INFO] Using columns: '{actual_x}' (x) and '{actual_y}' (y)")
  142.  
  143. # Optional bubble size column lookup (case-insensitive)
  144. size_col_actual = None
  145. if bubble_size_col:
  146. sz_l = bubble_size_col.lower()
  147. if sz_l in cols_lower:
  148. size_col_actual = cols[cols_lower.index(sz_l)]
  149. print(f"[INFO] Using bubble size column: '{size_col_actual}'")
  150. else:
  151. print(f"[WARN] bubble_size_col '{bubble_size_col}' not found; ignoring it.")
  152.  
  153. # Coerce to numeric (safe) and drop invalid rows
  154. df[actual_x] = pd.to_numeric(df[actual_x], errors='coerce')
  155. df[actual_y] = pd.to_numeric(df[actual_y], errors='coerce')
  156. df = df.replace([np.inf, -np.inf], np.nan)
  157. before = len(df)
  158. df = df.dropna(subset=[actual_x, actual_y])
  159. after = len(df)
  160. print(f"[INFO] Rows before: {before}, after cleaning numeric cols: {after} (dropped {before-after})")
  161. if after == 0:
  162. raise ValueError("No valid numeric rows remain after cleaning NaNs/inf.")
  163.  
  164. plt.figure(figsize=figsize)
  165. ct = chart_type.lower()
  166.  
  167. if ct == "scatter":
  168. plt.scatter(df[actual_x], df[actual_y], alpha=0.7, edgecolors='none')
  169. plt.title(f"Scatter Plot: {actual_x} vs {actual_y}")
  170.  
  171. elif ct == "line":
  172. df_sorted = df.sort_values(by=actual_x)
  173. plt.plot(df_sorted[actual_x], df_sorted[actual_y], marker='o')
  174. plt.title(f"Line Plot: {actual_x} vs {actual_y}")
  175.  
  176. elif ct == "hexbin":
  177. plt.hexbin(df[actual_x], df[actual_y], gridsize=30)
  178. cb = plt.colorbar()
  179. cb.set_label("Counts")
  180. plt.title(f"Hexbin Plot: {actual_x} vs {actual_y}")
  181.  
  182. elif ct == "bubble":
  183. # Use explicit bubble_size_col if provided; else derive from |y|
  184. if size_col_actual and size_col_actual in df.columns:
  185. df[size_col_actual] = pd.to_numeric(df[size_col_actual], errors='coerce').fillna(df[actual_y].abs())
  186. sizes_raw = df[size_col_actual].abs()
  187. else:
  188. sizes_raw = df[actual_y].abs().fillna(0.0)
  189. if sizes_raw.max() == 0:
  190. sizes = np.full(len(sizes_raw), 50.0)
  191. else:
  192. sizes = 50 + (sizes_raw / sizes_raw.max()) * 450
  193. plt.scatter(df[actual_x], df[actual_y], s=sizes, alpha=0.6, edgecolors='w', linewidth=0.3)
  194. plt.title(f"Bubble Chart (size ~ magnitude): {actual_x} vs {actual_y}")
  195.  
  196. elif ct == "density":
  197. try:
  198. from scipy.stats import gaussian_kde
  199. xy = np.vstack([df[actual_x].values, df[actual_y].values])
  200. kde = gaussian_kde(xy)
  201. z = kde(xy)
  202. idx = z.argsort()
  203. x_sorted = df[actual_x].values[idx]
  204. y_sorted = df[actual_y].values[idx]
  205. z_sorted = z[idx]
  206. plt.scatter(x_sorted, y_sorted, c=z_sorted, cmap='viridis', s=20, edgecolors='none')
  207. cb = plt.colorbar()
  208. cb.set_label("Density")
  209. plt.title(f"2D Density (KDE): {actual_x} vs {actual_y}")
  210. except Exception as e:
  211. print(f"[WARN] gaussian_kde failed or scipy missing ({e}). Falling back to hexbin.")
  212. plt.hexbin(df[actual_x], df[actual_y], gridsize=30)
  213. cb = plt.colorbar()
  214. cb.set_label("Counts")
  215. plt.title(f"2D Density (hexbin fallback): {actual_x} vs {actual_y}")
  216.  
  217. else:
  218. raise ValueError(f"Unsupported chart type '{chart_type}'. Supported: scatter, line, hexbin, bubble, density.")
  219.  
  220. plt.xlabel(actual_x)
  221. plt.ylabel(actual_y)
  222. plt.tight_layout()
  223.  
  224. filename = _timestamped_filename(output_file)
  225. plt.savefig(filename)
  226. plt.close()
  227. print(f"[SUCCESS] (NumericVsNumeric) Chart saved: {filename} (type: {chart_type})")
  228. return filename
  229.  
  230. except Exception as e:
  231. print(f"[ERROR] (NumericVsNumeric) {e}")
  232. return None
  233.  
  234.  
  235. def plot_two_columns_NumericVsCategorical(csv_file, col_cat, col_num, chart_type, output_file="chart.png", figsize=(8,6), max_categories=30):
  236. try:
  237. print(f"[INFO] (NumericVsCategorical) Reading CSV: {csv_file}")
  238. try:
  239. df = pd.read_csv(csv_file, encoding='utf-8')
  240. print("[INFO] Read with encoding='utf-8'")
  241. except UnicodeDecodeError:
  242. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  243. print("[INFO] Read with encoding='ISO-8859-1'")
  244.  
  245. cols = df.columns
  246. cols_lower = [c.lower() for c in cols]
  247. cat_l = col_cat.lower()
  248. num_l = col_num.lower()
  249.  
  250. if cat_l not in cols_lower or num_l not in cols_lower:
  251. raise KeyError(f"One or both columns not found. Available: {list(df.columns)}")
  252.  
  253. actual_cat = cols[cols_lower.index(cat_l)]
  254. actual_num = cols[cols_lower.index(num_l)]
  255. print(f"[INFO] Using columns: '{actual_cat}' (category) and '{actual_num}' (numeric)")
  256.  
  257. # Coerce numeric column
  258. df[actual_num] = pd.to_numeric(df[actual_num], errors='coerce')
  259. # Prepare categorical column as string and fill missing
  260. df[actual_cat] = df[actual_cat].astype("string").fillna("Missing")
  261. # Clean inf
  262. df = df.replace([np.inf, -np.inf], np.nan)
  263.  
  264. before = len(df)
  265. df = df.dropna(subset=[actual_num])
  266. after = len(df)
  267. print(f"[INFO] Rows before: {before}, after dropping invalid numeric rows: {after} (dropped {before-after})")
  268. if after == 0:
  269. raise ValueError("No valid numeric rows remain after cleaning NaNs/inf.")
  270.  
  271. # Collapse low-frequency categories if too many unique
  272. n_unique = df[actual_cat].nunique()
  273. print(f"[INFO] '{actual_cat}' unique categories: {n_unique}")
  274. if n_unique > max_categories:
  275. print(f"[WARN] High-cardinality categories ({n_unique}) — collapsing to top {max_categories} + 'Other'.")
  276. top = df[actual_cat].value_counts().nlargest(max_categories).index
  277. df[actual_cat] = df[actual_cat].where(df[actual_cat].isin(top), other="Other")
  278. print(f"[INFO] Categories after collapsing: {df[actual_cat].nunique()}")
  279.  
  280. sns.set(style="whitegrid")
  281. plt.figure(figsize=figsize)
  282. ct = chart_type.lower()
  283.  
  284. if ct.startswith("bar"):
  285. agg = "mean"
  286. if ":" in ct:
  287. _, suffix = ct.split(":",1)
  288. if suffix.strip() in ("sum","mean"):
  289. agg = suffix.strip()
  290. if agg == "sum":
  291. agg_df = df.groupby(actual_cat, dropna=False)[actual_num].sum().sort_values(ascending=False)
  292. else:
  293. agg_df = df.groupby(actual_cat, dropna=False)[actual_num].mean().sort_values(ascending=False)
  294. sns.barplot(x=agg_df.index, y=agg_df.values)
  295. plt.xlabel(actual_cat)
  296. plt.ylabel(f"{agg}({actual_num})")
  297. plt.title(f"Bar Plot ({agg}): {actual_num} by {actual_cat}")
  298. plt.xticks(rotation=45, ha="right")
  299.  
  300. elif ct == "box":
  301. sns.boxplot(x=actual_cat, y=actual_num, data=df)
  302. plt.title(f"Box Plot: {actual_num} by {actual_cat}")
  303. plt.xticks(rotation=45, ha="right")
  304.  
  305. elif ct == "violin":
  306. sns.violinplot(x=actual_cat, y=actual_num, data=df, inner="quartile")
  307. plt.title(f"Violin Plot: {actual_num} by {actual_cat}")
  308. plt.xticks(rotation=45, ha="right")
  309.  
  310. elif ct == "strip":
  311. sns.stripplot(x=actual_cat, y=actual_num, data=df, jitter=True, alpha=0.6)
  312. plt.title(f"Strip Plot: {actual_num} by {actual_cat}")
  313. plt.xticks(rotation=45, ha="right")
  314.  
  315. elif ct == "swarm":
  316. # Avoid seaborn swarmplot warning when categories are very crowded.
  317. # If the largest category has too many points, fallback to stripplot (no warning).
  318. max_count_per_cat = df[actual_cat].value_counts().max()
  319. total_points = len(df)
  320. # thresholds chosen conservatively to avoid the warning Seaborn emits
  321. if max_count_per_cat > 250 or total_points > 5000:
  322. print(f"[INFO] Large category size (max {max_count_per_cat}) or total points ({total_points}) - using stripplot fallback to avoid swarm placement issues.")
  323. sns.stripplot(x=actual_cat, y=actual_num, data=df, jitter=True, alpha=0.6)
  324. plt.title(f"Swarm Plot (fallback - strip): {actual_num} by {actual_cat}")
  325. plt.xticks(rotation=45, ha="right")
  326. else:
  327. try:
  328. sns.swarmplot(x=actual_cat, y=actual_num, data=df)
  329. plt.title(f"Swarm Plot: {actual_num} by {actual_cat}")
  330. plt.xticks(rotation=45, ha="right")
  331. except Exception as e:
  332. print(f"[WARN] swarmplot failed ({e}); falling back to stripplot with jitter.")
  333. sns.stripplot(x=actual_cat, y=actual_num, data=df, jitter=True, alpha=0.6)
  334. plt.title(f"Swarm Plot (fallback - strip): {actual_num} by {actual_cat}")
  335. plt.xticks(rotation=45, ha="right")
  336.  
  337. elif ct == "point":
  338. # use new seaborn parameter errorbar instead of deprecated ci
  339. order = df[actual_cat].value_counts().index
  340. try:
  341. sns.pointplot(x=actual_cat, y=actual_num, data=df, order=order, errorbar='sd')
  342. except TypeError:
  343. # older seaborn might not support errorbar param; fallback to ci if available
  344. sns.pointplot(x=actual_cat, y=actual_num, data=df, order=order, ci="sd")
  345. plt.title(f"Point Plot (mean ± sd): {actual_num} by {actual_cat}")
  346. plt.xticks(rotation=45, ha="right")
  347.  
  348. elif ct == "dot":
  349. categories = list(df[actual_cat].unique())
  350. cat_to_x = {c:i for i,c in enumerate(categories)}
  351. x = df[actual_cat].map(cat_to_x)
  352. jitter = np.random.normal(0, 0.08, size=len(x))
  353. plt.scatter(x + jitter, df[actual_num], s=10, alpha=0.6)
  354. plt.xticks(ticks=range(len(categories)), labels=categories, rotation=45, ha="right")
  355. plt.xlabel(actual_cat)
  356. plt.ylabel(actual_num)
  357. plt.title(f"Dot Plot: {actual_num} by {actual_cat}")
  358.  
  359. else:
  360. raise ValueError(f"Unsupported chart type '{chart_type}'. Supported: bar, bar:sum, box, violin, strip, swarm, point, dot.")
  361.  
  362. plt.tight_layout()
  363. filename = _timestamped_filename(output_file)
  364. plt.savefig(filename)
  365. plt.close()
  366. print(f"[SUCCESS] (NumericVsCategorical) Chart saved: {filename} (type: {chart_type})")
  367. return filename
  368.  
  369. except Exception as e:
  370. print(f"[ERROR] (NumericVsCategorical) {e}")
  371. return None
  372.  
  373.  
  374. def plot_two_columns_CategoricalVsCategorical(csv_file, col_a, col_b, chart_type, output_file="chart.png", figsize=(10,6), max_categories=30):
  375. try:
  376. print(f"[INFO] (CategoricalVsCategorical) Reading CSV: {csv_file}")
  377. try:
  378. df = pd.read_csv(csv_file, encoding="utf-8")
  379. print("[INFO] Read with encoding='utf-8'")
  380. except UnicodeDecodeError:
  381. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  382. print("[INFO] Read with encoding='ISO-8859-1'")
  383.  
  384. a = _match_column(df, col_a)
  385. b = _match_column(df, col_b)
  386. print(f"[INFO] Using columns: '{a}' and '{b}'")
  387.  
  388. # treat categories as strings, fill missing
  389. df[a] = df[a].astype("string").fillna("Missing")
  390. df[b] = df[b].astype("string").fillna("Missing")
  391. df = df.replace([np.inf, -np.inf], np.nan)
  392.  
  393. # collapse high-cardinality columns (applied to both)
  394. for c in (a, b):
  395. n_unique = df[c].nunique()
  396. if n_unique > max_categories:
  397. print(f"[WARN] Column '{c}' has {n_unique} categories; collapsing to top {max_categories} + 'Other'.")
  398. top = df[c].value_counts().nlargest(max_categories).index
  399. df[c] = df[c].where(df[c].isin(top), other="Other")
  400.  
  401. counts = df.groupby([a, b]).size().unstack(fill_value=0)
  402. plt.figure(figsize=figsize)
  403. ct = chart_type.lower()
  404.  
  405. if ct == "grouped_bar":
  406. counts.plot(kind="bar", figsize=figsize)
  407. plt.title(f"Grouped Bar: {a} vs {b}")
  408. plt.xlabel(a)
  409. plt.ylabel("Count")
  410. plt.xticks(rotation=45, ha="right")
  411.  
  412. elif ct == "stacked_bar":
  413. counts.plot(kind="bar", stacked=True, figsize=figsize)
  414. plt.title(f"Stacked Bar: {a} vs {b}")
  415. plt.xlabel(a)
  416. plt.ylabel("Count")
  417. plt.xticks(rotation=45, ha="right")
  418.  
  419. elif ct == "mosaic":
  420. try:
  421. from statsmodels.graphics.mosaicplot import mosaic
  422. props = {(str(k1), str(k2)): int(v) for k1, row in counts.iterrows() for k2, v in row.items()}
  423. plt.figure(figsize=figsize)
  424. mosaic(props, title=f"Mosaic Plot: {a} vs {b}")
  425. except Exception as e:
  426. print(f"[WARN] statsmodels mosaic unavailable or failed ({e}). Falling back to heatmap.")
  427. sns.heatmap(counts, annot=True, fmt="d")
  428. plt.title(f"Heatmap (mosaic fallback): {a} vs {b}")
  429.  
  430. elif ct == "clustered_dot":
  431. flat = counts.reset_index().melt(id_vars=a, var_name=b, value_name="count")
  432. flat = flat[flat["count"] > 0].reset_index(drop=True)
  433. a_map = {v:i for i,v in enumerate(sorted(df[a].unique()))}
  434. b_map = {v:i for i,v in enumerate(sorted(df[b].unique()))}
  435. x = flat[a].map(a_map).astype(float)
  436. y = flat[b].map(b_map).astype(float)
  437. sizes = 50 + (flat["count"] / flat["count"].max()) * 450
  438. jitter_x = np.random.normal(0, 0.08, size=len(x))
  439. jitter_y = np.random.normal(0, 0.08, size=len(y))
  440. plt.figure(figsize=figsize)
  441. plt.scatter(x + jitter_x, y + jitter_y, s=sizes, alpha=0.7)
  442. plt.xticks(ticks=list(a_map.values()), labels=list(a_map.keys()), rotation=45, ha="right")
  443. plt.yticks(ticks=list(b_map.values()), labels=list(b_map.keys()))
  444. plt.xlabel(a)
  445. plt.ylabel(b)
  446. plt.title(f"Clustered dot (counts): {a} vs {b}")
  447.  
  448. else:
  449. raise ValueError("Unsupported chart_type. Choose one of: grouped_bar, stacked_bar, mosaic, clustered_dot.")
  450.  
  451. plt.tight_layout()
  452. out = _timestamped_filename(output_file)
  453. plt.savefig(out)
  454. plt.close()
  455. print(f"[SUCCESS] (CategoricalVsCategorical) Saved: {out}")
  456. return out
  457.  
  458. except Exception as e:
  459. print(f"[ERROR] (CategoricalVsCategorical) {e}")
  460. return None
  461.  
  462.  
  463. def plot_two_columns_NumericVsDatetime(csv_file, col_date, col_num, chart_type, output_file="chart.png", figsize=(10,5), date_parse_format=None, rolling_window=7):
  464. try:
  465. print(f"[INFO] (NumericVsDatetime) Reading CSV: {csv_file}")
  466. try:
  467. df = pd.read_csv(csv_file, encoding='utf-8')
  468. print("[INFO] Read with encoding='utf-8'")
  469. except UnicodeDecodeError:
  470. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  471. print("[INFO] Read with encoding='ISO-8859-1'")
  472.  
  473. dcol = _match_column(df, col_date)
  474. ncol = _match_column(df, col_num)
  475. print(f"[INFO] Using columns: date='{dcol}', numeric='{ncol}'")
  476.  
  477. # parse dates (no infer_datetime_format parameter)
  478. df[dcol] = pd.to_datetime(df[dcol], format=date_parse_format, errors="coerce")
  479. df[ncol] = pd.to_numeric(df[ncol], errors='coerce')
  480. df = df.replace([np.inf, -np.inf], np.nan).dropna(subset=[dcol, ncol])
  481. if df.empty:
  482. raise ValueError("No valid rows after parsing datetime and numeric columns.")
  483.  
  484. df = df.sort_values(by=dcol).set_index(dcol)
  485.  
  486. plt.figure(figsize=figsize)
  487. ct = chart_type.lower()
  488.  
  489. if ct == "line":
  490. df[ncol].plot.line()
  491. plt.title(f"Line: {ncol} over time")
  492. plt.xlabel("Date")
  493. plt.ylabel(ncol)
  494.  
  495. elif ct == "area":
  496. df[ncol].plot.area()
  497. plt.title(f"Area: {ncol} over time")
  498. plt.xlabel("Date")
  499. plt.ylabel(ncol)
  500.  
  501. elif ct == "scatter":
  502. plt.scatter(df.index, df[ncol], alpha=0.7)
  503. plt.title(f"Scatter: {ncol} vs Date")
  504. plt.xlabel("Date")
  505. plt.ylabel(ncol)
  506.  
  507. elif ct == "rolling":
  508. daily = df[ncol].resample("D").mean()
  509. rolling = daily.rolling(window=rolling_window, min_periods=1).mean()
  510. plt.plot(daily.index, daily.values, marker='.', alpha=0.4, label="daily")
  511. plt.plot(rolling.index, rolling.values, marker=None, linewidth=2, label=f"rolling_{rolling_window}")
  512. plt.legend()
  513. plt.title(f"Rolling average (window={rolling_window}): {ncol}")
  514. plt.xlabel("Date")
  515. plt.ylabel(ncol)
  516. else:
  517. raise ValueError("Unsupported chart_type. Choose one of: line, area, scatter, rolling.")
  518.  
  519. plt.tight_layout()
  520. out = _timestamped_filename(output_file)
  521. plt.savefig(out)
  522. plt.close()
  523. print(f"[SUCCESS] (NumericVsDatetime) Saved: {out}")
  524. return out
  525.  
  526. except Exception as e:
  527. print(f"[ERROR] (NumericVsDatetime) {e}")
  528. return None
  529.  
  530.  
  531. def plot_two_columns_CategoricalVsDatetime(csv_file, col_date, col_cat, chart_type, output_file="chart.png", figsize=(12,6), date_parse_format=None, resample_freq="D", max_categories=20):
  532. try:
  533. print(f"[INFO] (CategoricalVsDatetime) Reading CSV: {csv_file}")
  534. try:
  535. df = pd.read_csv(csv_file, encoding='utf-8')
  536. print("[INFO] Read with encoding='utf-8'")
  537. except UnicodeDecodeError:
  538. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  539. print("[INFO] Read with encoding='ISO-8859-1'")
  540.  
  541. dcol = _match_column(df, col_date)
  542. ccol = _match_column(df, col_cat)
  543. print(f"[INFO] Using columns: date='{dcol}', category='{ccol}'")
  544.  
  545. # parse dates (no infer_datetime_format)
  546. df[dcol] = pd.to_datetime(df[dcol], format=date_parse_format, errors="coerce")
  547. df[ccol] = df[ccol].astype("string").fillna("Missing")
  548. df = df.replace([np.inf, -np.inf], np.nan).dropna(subset=[dcol])
  549. if df.empty:
  550. raise ValueError("No valid rows after parsing date column.")
  551.  
  552. if df[ccol].nunique() > max_categories:
  553. print(f"[WARN] collapsing categories to top {max_categories}+Other")
  554. top = df[ccol].value_counts().nlargest(max_categories).index
  555. df[ccol] = df[ccol].where(df[ccol].isin(top), other="Other")
  556.  
  557. ct = chart_type.lower()
  558.  
  559. if ct == "timeline":
  560. categories = sorted(df[ccol].unique())
  561. cat_to_y = {c:i for i,c in enumerate(categories)}
  562. y = df[ccol].map(cat_to_y)
  563. x = df[dcol]
  564. plt.figure(figsize=figsize)
  565. jitter = np.random.normal(0, 0.08, size=len(y))
  566. plt.scatter(x, y + jitter, alpha=0.7)
  567. plt.yticks(ticks=list(cat_to_y.values()), labels=list(cat_to_y.keys()))
  568. plt.xlabel("Date")
  569. plt.title(f"Timeline of {ccol}")
  570. plt.tight_layout()
  571.  
  572. elif ct == "stacked_area":
  573. df2 = df.set_index(dcol)
  574. counts = df2.groupby([pd.Grouper(freq=resample_freq), ccol]).size().unstack(fill_value=0)
  575. counts.plot.area(figsize=figsize)
  576. plt.title(f"Stacked area ({resample_freq}) of {ccol} over time")
  577. plt.xlabel("Time")
  578. plt.ylabel("Count")
  579. plt.tight_layout()
  580.  
  581. else:
  582. raise ValueError("Unsupported chart_type. Choose 'timeline', or 'stacked_area'.")
  583.  
  584. out = _timestamped_filename(output_file)
  585. plt.savefig(out)
  586. plt.close()
  587. print(f"[SUCCESS] (CategoricalVsDatetime) Saved: {out}")
  588. return out
  589.  
  590. except Exception as e:
  591. print(f"[ERROR] (CategoricalVsDatetime) {e}")
  592. return None
  593.  
  594.  
  595. def plot_two_columns_DatetimeVsDatetime(csv_file, col_start, col_end, chart_type, output_file="chart.png", figsize=(10,6), date_parse_format=None):
  596. try:
  597. print(f"[INFO] (DatetimeVsDatetime) Reading CSV: {csv_file}")
  598. try:
  599. df = pd.read_csv(csv_file, encoding='utf-8')
  600. print("[INFO] Read with encoding='utf-8'")
  601. except UnicodeDecodeError:
  602. df = pd.read_csv(csv_file, encoding='ISO-8859-1')
  603. print("[INFO] Read with encoding='ISO-8859-1'")
  604.  
  605. s = _match_column(df, col_start)
  606. e = _match_column(df, col_end)
  607. print(f"[INFO] Using columns: start='{s}', end='{e}'")
  608.  
  609. df[s] = pd.to_datetime(df[s], format=date_parse_format, errors='coerce')
  610. df[e] = pd.to_datetime(df[e], format=date_parse_format, errors='coerce')
  611. df = df.replace([np.inf, -np.inf], np.nan).dropna(subset=[s,e])
  612. if df.empty:
  613. raise ValueError("No valid start/end rows after parsing dates.")
  614.  
  615. ct = chart_type.lower()
  616. plt.figure(figsize=figsize)
  617.  
  618. if ct == "gantt":
  619. df_sorted = df.sort_values(by=s).reset_index(drop=True)
  620. starts = pd.to_datetime(df_sorted[s])
  621. ends = pd.to_datetime(df_sorted[e])
  622. durations = (ends - starts).dt.total_seconds() / (60*60*24) # days
  623. y = np.arange(len(df_sorted))
  624. # left must be numeric; convert to matplotlib datenum
  625. left = starts.map(datetime.toordinal)
  626. plt.barh(y, durations, left=left, height=0.6)
  627. xticks = plt.xticks()[0]
  628. xtick_labels = [datetime.fromordinal(int(x)).strftime("%Y-%m-%d") for x in xticks]
  629. plt.gca().set_yticks(y)
  630. plt.gca().set_yticklabels([f"row_{i}" for i in y])
  631. plt.xlabel("Date")
  632. plt.title("Gantt-like chart (start vs duration in days)")
  633. plt.tight_layout()
  634.  
  635. elif ct == "duration_histogram":
  636. durations_days = (df[e] - df[s]).dt.total_seconds() / (60*60*24)
  637. plt.hist(durations_days.dropna(), bins=30)
  638. plt.xlabel("Duration (days)")
  639. plt.ylabel("Count")
  640. plt.title("Duration histogram (end - start)")
  641. plt.tight_layout()
  642.  
  643. elif ct == "scatter":
  644. x = df[s].map(datetime.toordinal)
  645. y = df[e].map(datetime.toordinal)
  646. plt.scatter(x, y, alpha=0.7)
  647. plt.xlabel("Start (ordinal)")
  648. plt.ylabel("End (ordinal)")
  649. plt.title("Start vs End scatter (ordinal dates)")
  650. plt.tight_layout()
  651. else:
  652. raise ValueError("Unsupported chart_type. Choose 'gantt', 'duration_histogram', or 'scatter'.")
  653.  
  654. out = _timestamped_filename(output_file)
  655. plt.savefig(out)
  656. plt.close()
  657. print(f"[SUCCESS] (DatetimeVsDatetime) Saved: {out}")
  658. return out
  659.  
  660. except Exception as e:
  661. print(f"[ERROR] (DatetimeVsDatetime) {e}")
  662. return None
  663.  
  664. # ---------------- MAIN: run all scenarios using existing mydata.csv ----------------
  665. if __name__ == "__main__":
  666. csv_path = "mydata.csv"
  667. if not os.path.exists(csv_path):
  668. print("[FATAL] mydata.csv not found in current directory. Place your dataset as 'mydata.csv' and re-run.")
  669. raise SystemExit(1)
  670.  
  671. # Try to load once to inspect available columns and types
  672. try:
  673. df_master = pd.read_csv(csv_path, encoding='utf-8')
  674. except UnicodeDecodeError:
  675. df_master = pd.read_csv(csv_path, encoding='ISO-8859-1')
  676.  
  677. cols = list(df_master.columns)
  678. cols_lower = [c.lower() for c in cols]
  679. print(f"[INFO] Found columns: {cols}")
  680.  
  681.  
  682. # Test all columns with universal function
  683. chart_types = ["bar", "count", "pie"]
  684.  
  685. # Test a few sample columns (you can modify this to test specific columns)
  686. sample_columns = ['PRICEEACH','ORDERDATE','STATUS']
  687.  
  688. for col in sample_columns:
  689. for ct in chart_types:
  690. out = plot_one_column_Universal(csv_path, col, ct, output_file=f"mydata_universal_{ct}_{col}.png")
  691. if out:
  692. print(f"[INFO] Produced universal chart: {out}")
  693.  
  694. # Candidate column names from your provided schema (case-insensitive)
  695. candidates = {
  696. "numeric": ["QUANTITYORDERED", "PRICEEACH", "ORDERLINENUMBER", "SALES", "QTR_ID", "MONTH_ID", "YEAR_ID", "MSRP"],
  697. "categorical": ["STATUS", "PRODUCTLINE", "PRODUCTCODE", "CUSTOMERNAME", "COUNTRY", "TERRITORY", "DEALSIZE"],
  698. "datetime": ["ORDERDATE"],
  699. "text": ["ADDRESSLINE1", "CUSTOMERNAME", "CONTACTFIRSTNAME", "CONTACTLASTNAME"]
  700. }
  701.  
  702. # helper: pick the first available from candidates
  703. def pick_first_available(lst):
  704. for name in lst:
  705. if name.lower() in cols_lower:
  706. return cols[cols_lower.index(name.lower())]
  707. return None
  708.  
  709. # ---------- Numeric vs Numeric ----------
  710. nn_x = pick_first_available(candidates["numeric"])
  711. # pick a different numeric column for y if possible
  712. nn_y = None
  713. if nn_x:
  714. for n in candidates["numeric"]:
  715. if n.lower() in cols_lower and cols[cols_lower.index(n.lower())] != nn_x:
  716. nn_y = cols[cols_lower.index(n.lower())]
  717. break
  718.  
  719. if nn_x and nn_y:
  720. nn_chart_types = ["scatter", "line", "hexbin", "bubble", "density"]
  721. for ct in nn_chart_types:
  722. out = plot_two_columns_NumericVsNumeric(csv_path, nn_x, nn_y, ct, output_file=f"mydata_nn_{ct}.png")
  723. if out:
  724. print(f"[INFO] Produced numeric-numeric: {out}")
  725. else:
  726. print("[WARN] Not enough numeric columns found for Numeric vs Numeric tests. Found:", nn_x, nn_y)
  727.  
  728. # ---------- Numeric vs Categorical ----------
  729. cat_col = pick_first_available(candidates["categorical"])
  730. num_col = pick_first_available(candidates["numeric"])
  731. if cat_col and num_col:
  732. nc_chart_types = ["bar", "bar:sum", "box", "violin", "strip", "swarm", "point", "dot"]
  733. for ct in nc_chart_types:
  734. out = plot_two_columns_NumericVsCategorical(csv_path, cat_col, num_col, ct, output_file=f"mydata_nc_{ct}.png")
  735. if out:
  736. print(f"[INFO] Produced numeric-categorical: {out}")
  737. else:
  738. print("[WARN] Missing categorical or numeric column for Numeric vs Categorical tests. Found:", cat_col, num_col)
  739.  
  740. # ---------- Categorical vs Categorical ----------
  741. cc_a = pick_first_available(candidates["categorical"])
  742. cc_b = None
  743. if cc_a:
  744. for c in candidates["categorical"]:
  745. if c.lower() in cols_lower and cols[cols_lower.index(c.lower())] != cc_a:
  746. cc_b = cols[cols_lower.index(c.lower())]
  747. break
  748. if cc_a and cc_b:
  749. cc_chart_types = ["grouped_bar", "stacked_bar", "mosaic", "clustered_dot"]
  750. for ct in cc_chart_types:
  751. out = plot_two_columns_CategoricalVsCategorical(csv_path, cc_a, cc_b, ct, output_file=f"mydata_cc_{ct}.png")
  752. if out:
  753. print(f"[INFO] Produced categorical-categorical: {out}")
  754. else:
  755. print("[WARN] Not enough categorical columns found for Categorical vs Categorical tests. Found:", cc_a, cc_b)
  756.  
  757. # ---------- Numeric vs Datetime ----------
  758. dt_col = pick_first_available(candidates["datetime"])
  759. num_col = pick_first_available(candidates["numeric"])
  760. if dt_col and num_col:
  761. nd_chart_types = ["line", "area", "scatter", "rolling"]
  762. for ct in nd_chart_types:
  763. out = plot_two_columns_NumericVsDatetime(csv_path, dt_col, num_col, ct, output_file=f"mydata_nd_{ct}.png", rolling_window=7)
  764. if out:
  765. print(f"[INFO] Produced numeric-datetime: {out}")
  766. else:
  767. print("[WARN] Missing datetime or numeric column for Numeric vs Datetime tests. Found:", dt_col, num_col)
  768.  
  769. # ---------- Categorical vs Datetime ----------
  770. dt_col = pick_first_available(candidates["datetime"])
  771. cat_col = pick_first_available(candidates["categorical"])
  772. if dt_col and cat_col:
  773. cd_chart_types = ["timeline", "stacked_area"]
  774. for ct in cd_chart_types:
  775. out = plot_two_columns_CategoricalVsDatetime(csv_path, dt_col, cat_col, ct, output_file=f"mydata_cd_{ct}.png", resample_freq="W")
  776. if out:
  777. print(f"[INFO] Produced categorical-datetime: {out}")
  778. else:
  779. print("[WARN] Missing datetime or categorical column for Categorical vs Datetime tests. Found:", dt_col, cat_col)
  780.  
  781. # ---------- Datetime vs Datetime ----------
  782. date_cols = []
  783. for c in df_master.columns:
  784. if "date" in c.lower():
  785. date_cols.append(c)
  786. if len(date_cols) >= 2:
  787. d1, d2 = date_cols[0], date_cols[1]
  788. dd_chart_types = ["gantt", "duration_histogram", "scatter"]
  789. for ct in dd_chart_types:
  790. out = plot_two_columns_DatetimeVsDatetime(csv_path, d1, d2, ct, output_file=f"mydata_dd_{ct}.png")
  791. if out:
  792. print(f"[INFO] Produced datetime-datetime: {out}")
  793. else:
  794. print("[WARN] Not enough datetime columns for Datetime vs Datetime tests. Found date-like columns:", date_cols)
  795.  
  796.  
  797. print("[DONE] All requested chart attempts completed. Check saved PNG files in the 'plots' folder.")
  798.  
Advertisement
Add Comment
Please, Sign In to add comment