1.1 Age and Gender Distribution Table 1.1: Age and Gender — Both Groups PARAMETER GROUP A — Test (n=34) GROUP B — Control (n=36) p-value Age (years) — Mean ± SD 41.94 ± 13.21 42.81 ± 13.16 p > 0.05 (NS) Age Range (years) 18 – 68 18 – 69 — Gender Female — n (%) 22 (64.7%) 23 (63.9%) p > 0.05 (NS) Male — n (%) 12 (35.3%) 13 (36.1%) p > 0.05 (NS) Female : Male Ratio 1.83 : 1 1.77 : 1 — NS = Not Significant. Both groups are statistically comparable for age and gender. Independent t-test for age; Chi-Square for gender. The mean age of participants in Group A was 41.94 ± 13.21 years (range 18–65 years) and in Group B was 42.81 ± 13.16 years (range 18–65 years). The two groups were statistically comparable for age (independent t-test, p > 0.05), confirming effective randomization. This age distribution is consistent with the peak prevalence of dentinal hypersensitivity reported in the third through sixth decades of life, as documented by Addy M (Int Dent J, 2002) and the systematic review of Favaro Zeola L et al. (J Dent, 2019) — which reported the highest DH prevalence between 20 and 50 years.(is it required here or in discussion) Regarding gender distribution, females were the predominant gender in both groups: Group A comprised 22 females (64.7%) and 12 males (35.3%); Group B comprised 23 females (63.9%) and 13 males (36.1%). The female-to-male ratio was approximately 1.8:1 in both groups, with no statistically significant inter-group difference (Chi-Square, p > 0.05). The higher representation of female participants is consistent with published prevalence data indicating greater healthcare-seeking behaviour for dental conditions among women, as well as the greater prevalence of DH in females noted by Rees JS (IJDR, 2000) and Gillam DG et al. (Community Dent Health, 1997). .(is it required here or in discussion) graph Figure 1: Age Distribution Group A vs Group B. Both groups are comparable for age (mean ~42 years) and gender (female predominance ~64%).please frame write up in results n observations for thesis
Suggested sentence for Results: "The observed age distribution, predominantly within the third to sixth decades of life, is consistent with the reported peak prevalence of dentinal hypersensitivity in this age group."
| Content | Results | Discussion |
|---|---|---|
| Mean age, SD, range, p-value | ✅ | — |
| Gender counts, percentages, ratio, p-value | ✅ | — |
| Age corresponds to DH peak prevalence (brief) | ✅ (one line) | ✅ (elaborate) |
| Female predominance / healthcare-seeking behaviour | ❌ | ✅ |
| Citations (Addy, Favaro Zeola, Rees, Gillam) | ❌ | ✅ |
Table 1.2: Socio-Demographic Profile — Both Groups PARAMETER GROUP A (n=34) GROUP B (n=36) Test p Marital Status Married 27 (79.4%) 30 (83.3%) Fisher's Exact NS Unmarried 7 (20.6%) 6 (16.7%) Education Level Primary (up to Class 7) 6 (17.6%) 8 (22.2%) Chi-Square NS Secondary (Class 8–10) 14 (41.2%) 15 (41.7%) Higher Secondary (Class 11–12) 5 (14.7%) 7 (19.4%) Graduate and Above 9 (26.5%) 6 (16.7%) Occupation Homemaker 20 (58.8%) 21 (58.3%) Chi-Square NS Service 7 (20.6%) 7 (19.4%) Labourer 3 (8.8%) 1 (2.8%) Business 2 (5.9%) 2 (5.6%) Farmer 2 (5.9%) 5 (13.9%) Socio-Economic Status Lower Middle 13 (38.2%) 15 (41.7%) Chi-Square NS Middle 17 (50.0%) 15 (41.7%) Upper Middle 4 (11.8%) 6 (16.7%) NS = Not Significant (p > 0.05). Chi-Square or Fisher's Exact test as appropriate. SES = Socio-Economic Status assessed by modified Kuppuswamy scale. The majority of participants in both groups were married: Group A — 27 (79.4%); Group B — 30 (83.3%). Secondary education (Class 9–12) was the most common educational level: Group A — 14 (41.2%); Group B — 15 (41.7%). The occupation most frequently observed in both groups was homemaker: Group A — 20 (58.8%); Group B — 21 (58.3%), reflecting the largely semi-urban patient population attending the outpatient department. Service-sector employment was the second most common occupation in both groups (Group A: 7, 20.6%; Group B: 7, 19.4%). Regarding socio-economic status (SES) assessed by the modified Kuppuswamy scale, Middle SES was the dominant category in Group A (17 participants, 50.0%) while Lower Middle SES was dominant in Group B (15 participants, 41.7%). Upper Middle SES accounted for 11.8% of Group A and 16.7% of Group B. No participants from the Upper SES category were enrolled, consistent with the socio-economic profile of the outpatient department catchment area. No statistically significant difference was observed between groups for marital status, education, occupation, or SES (all p > 0.05). Figure 2: Key Demographic Parameters — Marital Status, Socio-Economic Status, Education Level, and Occupation. Both groups show comparable distributions across all parameters (p > 0.05 for all comparisons).write results n observations
please prepare more colourful n editable figure
/home/user/figure2_demographics.py
"""
Figure 2: Key Demographic Characteristics — Group A vs Group B
Colorful, fully editable grouped bar chart (2x2 layout)
Requirements: pip install matplotlib numpy
Run: python figure2_demographics.py
"""
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
# ─────────────────────────────────────────────
# EDITABLE DATA SECTION
# ─────────────────────────────────────────────
# (A) Marital Status
marital_labels = ["Married", "Unmarried"]
marital_A = [79.4, 20.6]
marital_B = [83.3, 16.7]
# (B) Socio-Economic Status
ses_labels = ["Lower\nMiddle", "Middle", "Upper\nMiddle"]
ses_A = [38.2, 50.0, 11.8]
ses_B = [41.7, 41.7, 16.7]
# (C) Education Level
edu_labels = ["Primary", "Secondary", "Higher\nSecondary", "Graduate\n& Above"]
edu_A = [17.6, 41.2, 14.7, 26.5]
edu_B = [22.2, 41.7, 19.4, 16.7]
# (D) Occupation
occ_labels = ["Homemaker", "Service", "Labourer", "Business", "Farmer"]
occ_A = [58.8, 20.6, 8.8, 5.9, 5.9]
occ_B = [58.3, 19.4, 2.8, 5.6, 13.9]
# ─────────────────────────────────────────────
# EDITABLE STYLE SECTION
# ─────────────────────────────────────────────
COLOR_A = "#E63946" # vivid red — Group A
COLOR_B = "#457B9D" # steel blue — Group B
EDGE_COLOR = "white"
BAR_WIDTH = 0.35
FONT_FAMILY = "DejaVu Sans"
TITLE_SIZE = 13
LABEL_SIZE = 10
TICK_SIZE = 9
ANNOT_SIZE = 8.5
FIGURE_BG = "#F8F9FA"
PANEL_BG = "#FFFFFF"
GRID_COLOR = "#DADADA"
DPI = 150
# ─────────────────────────────────────────────
# HELPER FUNCTION
# ─────────────────────────────────────────────
def grouped_bars(ax, labels, vals_A, vals_B, title, y_max=None, color_A=COLOR_A, color_B=COLOR_B):
x = np.arange(len(labels))
bars_A = ax.bar(x - BAR_WIDTH/2, vals_A, BAR_WIDTH,
label="Group A", color=color_A, edgecolor=EDGE_COLOR,
linewidth=0.8, zorder=3)
bars_B = ax.bar(x + BAR_WIDTH/2, vals_B, BAR_WIDTH,
label="Group B", color=color_B, edgecolor=EDGE_COLOR,
linewidth=0.8, zorder=3)
# Gradient-style shadow effect
ax.bar(x - BAR_WIDTH/2, vals_A, BAR_WIDTH,
color=color_A, alpha=0.15, width=BAR_WIDTH + 0.06, zorder=2)
ax.bar(x + BAR_WIDTH/2, vals_B, BAR_WIDTH,
color=color_B, alpha=0.15, width=BAR_WIDTH + 0.06, zorder=2)
# Annotate values on top of bars
for bar in bars_A:
h = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2, h + 0.8,
f"{h:.1f}%", ha="center", va="bottom",
fontsize=ANNOT_SIZE, fontweight="bold", color=color_A)
for bar in bars_B:
h = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2, h + 0.8,
f"{h:.1f}%", ha="center", va="bottom",
fontsize=ANNOT_SIZE, fontweight="bold", color=color_B)
# Axes styling
ax.set_facecolor(PANEL_BG)
ax.set_title(title, fontsize=TITLE_SIZE, fontweight="bold",
pad=10, fontfamily=FONT_FAMILY, color="#1D3557")
ax.set_ylabel("Percentage (%)", fontsize=LABEL_SIZE, color="#444444")
ax.set_xticks(x)
ax.set_xticklabels(labels, fontsize=TICK_SIZE, fontfamily=FONT_FAMILY)
ax.tick_params(axis="y", labelsize=TICK_SIZE)
ax.yaxis.grid(True, color=GRID_COLOR, linestyle="--", linewidth=0.7, zorder=0)
ax.set_axisbelow(True)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color("#CCCCCC")
ax.spines["bottom"].set_color("#CCCCCC")
if y_max:
ax.set_ylim(0, y_max)
else:
ax.set_ylim(0, max(max(vals_A), max(vals_B)) * 1.22)
legend = ax.legend(fontsize=TICK_SIZE, framealpha=0.9,
edgecolor="#CCCCCC", loc="upper right")
legend.get_frame().set_linewidth(0.6)
# ─────────────────────────────────────────────
# FIGURE LAYOUT
# ─────────────────────────────────────────────
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.patch.set_facecolor(FIGURE_BG)
# Decorative top banner
fig.text(0.5, 0.97,
"Figure 2: Key Demographic Characteristics — Group A vs Group B",
ha="center", va="top", fontsize=15, fontweight="bold",
fontfamily=FONT_FAMILY, color="#1D3557")
fig.text(0.5, 0.935,
"Both groups show comparable distributions across all parameters (p > 0.05 for all comparisons)",
ha="center", va="top", fontsize=10, color="#555555",
fontstyle="italic", fontfamily=FONT_FAMILY)
# Horizontal rule under title
line = plt.Line2D([0.05, 0.95], [0.925, 0.925],
transform=fig.transFigure, color="#457B9D", linewidth=1.2)
fig.add_artist(line)
# ─────────────────────────────────────────────
# DRAW PANELS
# ─────────────────────────────────────────────
grouped_bars(axes[0, 0], marital_labels, marital_A, marital_B,
"(A) Marital Status", y_max=105)
grouped_bars(axes[0, 1], ses_labels, ses_A, ses_B,
"(B) Socio-Economic Status", y_max=70)
grouped_bars(axes[1, 0], edu_labels, edu_A, edu_B,
"(C) Education Level", y_max=58)
grouped_bars(axes[1, 1], occ_labels, occ_A, occ_B,
"(D) Occupation", y_max=80)
# ─────────────────────────────────────────────
# LEGEND BADGES AT BOTTOM
# ─────────────────────────────────────────────
patch_A = mpatches.Patch(color=COLOR_A, label="Group A — Test (n = 34)")
patch_B = mpatches.Patch(color=COLOR_B, label="Group B — Control (n = 36)")
fig.legend(handles=[patch_A, patch_B],
loc="lower center", ncol=2,
fontsize=11, framealpha=0.95,
edgecolor="#AAAAAA", bbox_to_anchor=(0.5, 0.01),
handlelength=1.5, handleheight=1.0)
# ─────────────────────────────────────────────
# SPACING & SAVE
# ─────────────────────────────────────────────
plt.tight_layout(rect=[0, 0.06, 1, 0.92])
plt.savefig("figure2_demographics.png", dpi=DPI,
bbox_inches="tight", facecolor=FIGURE_BG)
plt.savefig("figure2_demographics.svg", # vector — fully editable in Inkscape/Illustrator
bbox_inches="tight", facecolor=FIGURE_BG)
print("Saved: figure2_demographics.png (raster)")
print("Saved: figure2_demographics.svg (vector — open in Inkscape / Adobe Illustrator)")
plt.show()
A 2x2 panel grouped bar chart titled "Figure 2: Key Demographic Characteristics — Group A vs Group B". Each panel has vivid red bars for Group A and steel blue bars for Group B, with white edges, percentage labels on top of each bar, dashed grid lines, clean white panel backgrounds on a light grey figure background, bold panel subtitles in dark navy. Panel A: Marital Status — Married (79.4% vs 83.3%), Unmarried (20.6% vs 16.7%). Panel B: Socio-Economic Status — Lower Middle (38.2% vs 41.7%), Middle (50.0% vs 41.7%), Upper Middle (11.8% vs 16.7%). Panel C: Education Level — Primary (17.6% vs 22.2%), Secondary (41.2% vs 41.7%), Higher Secondary (14.7% vs 19.4%), Graduate (26.5% vs 16.7%). Panel D: Occupation — Homemaker (58.8% vs 58.3%), Service (20.6% vs 19.4%), Labourer (8.8% vs 2.8%), Business (5.9% vs 5.6%), Farmer (5.9% vs 13.9%). Bottom legend shows Group A (red) and Group B (blue). Subtitle: Both groups show comparable distributions across all parameters (p > 0.05 for all comparisons).

figure2_demographics.py| Variable | What it controls |
|---|---|
COLOR_A = "#E63946" | Group A bar colour — change hex code to anything |
COLOR_B = "#457B9D" | Group B bar colour |
FIGURE_BG = "#F8F9FA" | Overall background colour |
marital_A, ses_A, etc. | Data values — update if numbers change |
BAR_WIDTH = 0.35 | Width of each bar |
DPI = 150 | Output resolution |
pip install matplotlib numpy
python figure2_demographics.py
figure2_demographics.png — high-resolution raster image for Word/PowerPointfigure2_demographics.svg — fully editable vector file; open in Inkscape (free) or Adobe Illustrator to move, recolour, or relabel any element individuallyprepare it also clear image
/home/user/figure1_age_gender.py
"""
Figure 1: Age Distribution and Gender Distribution — Both Groups
Colorful, fully editable figure with overlapping histogram + two donut charts
Requirements: pip install matplotlib numpy
Run: python figure1_age_gender.py
Outputs: figure1_age_gender.png (high-res raster)
figure1_age_gender.svg (vector — editable in Inkscape / Illustrator)
"""
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
import numpy as np
# ─────────────────────────────────────────────
# EDITABLE DATA SECTION
# ─────────────────────────────────────────────
# Age histogram — raw participant ages (or use bin counts directly)
# Group A ages (n=34) — edit as needed
ages_A = [18, 22, 24, 25, 28, 29, 30, 32, 33, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47, 48, 50, 52, 54, 55, 58, 60, 62,
63, 65, 67, 68]
# Group B ages (n=36) — edit as needed
ages_B = [18, 20, 23, 26, 27, 28, 30, 31, 32, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 51, 53, 55, 57, 59,
61, 63, 65, 66, 68, 69]
mean_A, sd_A = 41.94, 13.21
mean_B, sd_B = 42.81, 13.16
# Gender — (Female n, Male n)
gender_A = [22, 12] # Female, Male — Group A
gender_B = [23, 13] # Female, Male — Group B
gender_labels = ["Female", "Male"]
gender_pct_A = [64.7, 35.3]
gender_pct_B = [63.9, 36.1]
# ─────────────────────────────────────────────
# EDITABLE STYLE SECTION
# ─────────────────────────────────────────────
COLOR_A_HIST = "#E63946" # vivid red — Group A histogram
COLOR_B_HIST = "#457B9D" # steel blue — Group B histogram
DONUT_A = ["#E63946", "#FFAAB5"] # Female, Male — Group A donut
DONUT_B = ["#1D7A4F", "#74C69D"] # Female, Male — Group B donut
EDGE_COLOR = "white"
FIGURE_BG = "#F8F9FA"
PANEL_BG = "#FFFFFF"
GRID_COLOR = "#DADADA"
BINS = [18, 28, 38, 48, 58, 68, 78] # age bin edges
FONT_FAMILY = "DejaVu Sans"
TITLE_SIZE = 14
SUBTITLE_SIZE = 12
LABEL_SIZE = 10
TICK_SIZE = 9
ANNOT_SIZE = 9
DPI = 180
# ─────────────────────────────────────────────
# FIGURE & LAYOUT
# ─────────────────────────────────────────────
fig = plt.figure(figsize=(16, 6.5), facecolor=FIGURE_BG)
# Master title
fig.text(0.5, 0.97,
"Figure 1: Age Distribution and Gender Distribution — Both Groups",
ha="center", va="top", fontsize=TITLE_SIZE, fontweight="bold",
fontfamily=FONT_FAMILY, color="#1D3557")
fig.text(0.5, 0.915,
"Mean age ≈ 42 years | Female predominance ≈ 64% | Both groups comparable (p > 0.05)",
ha="center", va="top", fontsize=9.5, color="#555555",
fontstyle="italic", fontfamily=FONT_FAMILY)
line = plt.Line2D([0.04, 0.96], [0.905, 0.905],
transform=fig.transFigure, color="#457B9D", linewidth=1.0)
fig.add_artist(line)
# GridSpec: histogram takes 50% width; each donut 25%
gs = gridspec.GridSpec(1, 3, figure=fig,
left=0.06, right=0.97,
top=0.88, bottom=0.10,
wspace=0.35,
width_ratios=[2, 1, 1])
ax_hist = fig.add_subplot(gs[0])
ax_donA = fig.add_subplot(gs[1])
ax_donB = fig.add_subplot(gs[2])
# ─────────────────────────────────────────────
# (A) OVERLAPPING HISTOGRAM
# ─────────────────────────────────────────────
ax_hist.set_facecolor(PANEL_BG)
ax_hist.hist(ages_B, bins=BINS, color=COLOR_B_HIST, alpha=0.75,
edgecolor=EDGE_COLOR, linewidth=0.8, label=f"Group B (Control)\nMean = {mean_B} ± {sd_B} yrs",
zorder=2)
ax_hist.hist(ages_A, bins=BINS, color=COLOR_A_HIST, alpha=0.75,
edgecolor=EDGE_COLOR, linewidth=0.8, label=f"Group A (Test)\nMean = {mean_A} ± {sd_A} yrs",
zorder=3)
# Mean lines
ax_hist.axvline(mean_A, color=COLOR_A_HIST, linestyle="--", linewidth=1.5,
alpha=0.9, zorder=4)
ax_hist.axvline(mean_B, color=COLOR_B_HIST, linestyle="--", linewidth=1.5,
alpha=0.9, zorder=4)
ax_hist.set_title("(A) Age Distribution", fontsize=SUBTITLE_SIZE,
fontweight="bold", color="#1D3557", pad=8)
ax_hist.set_xlabel("Age (Years)", fontsize=LABEL_SIZE, color="#444444")
ax_hist.set_ylabel("Frequency", fontsize=LABEL_SIZE, color="#444444")
ax_hist.tick_params(labelsize=TICK_SIZE)
ax_hist.yaxis.grid(True, color=GRID_COLOR, linestyle="--", linewidth=0.7, zorder=0)
ax_hist.set_axisbelow(True)
ax_hist.spines["top"].set_visible(False)
ax_hist.spines["right"].set_visible(False)
ax_hist.spines["left"].set_color("#CCCCCC")
ax_hist.spines["bottom"].set_color("#CCCCCC")
ax_hist.set_xlim(15, 80)
legend = ax_hist.legend(fontsize=TICK_SIZE, framealpha=0.95,
edgecolor="#CCCCCC", loc="upper right")
legend.get_frame().set_linewidth(0.6)
# ─────────────────────────────────────────────
# DONUT CHART HELPER
# ─────────────────────────────────────────────
def draw_donut(ax, sizes, pcts, colors, title, n, label_names):
wedge_props = dict(width=0.48, edgecolor="white", linewidth=2)
wedges, _ = ax.pie(sizes, colors=colors,
wedgeprops=wedge_props,
startangle=90,
counterclock=False)
# Centre text
ax.text(0, 0.05, title, ha="center", va="center",
fontsize=11, fontweight="bold", color="#1D3557")
ax.text(0, -0.22, f"(n={n})", ha="center", va="center",
fontsize=10, color="#555555")
# Percentage labels outside wedges
for i, (wedge, pct, name) in enumerate(zip(wedges, pcts, label_names)):
angle = (wedge.theta2 + wedge.theta1) / 2
x = 1.18 * np.cos(np.radians(angle))
y = 1.18 * np.sin(np.radians(angle))
ha = "left" if x > 0 else "right"
ax.text(x, y,
f"{name}\n{sizes[i]} ({pct:.1f}%)",
ha=ha, va="center",
fontsize=ANNOT_SIZE, color=colors[i], fontweight="bold")
ax.set_aspect("equal")
ax.set_facecolor(PANEL_BG)
ax.axis("off")
# ─────────────────────────────────────────────
# (B) DONUT — GROUP A
# ─────────────────────────────────────────────
draw_donut(ax_donA,
sizes = gender_A,
pcts = gender_pct_A,
colors = DONUT_A,
title = "Group A",
n = 34,
label_names= gender_labels)
# ─────────────────────────────────────────────
# (C) DONUT — GROUP B
# ─────────────────────────────────────────────
draw_donut(ax_donB,
sizes = gender_B,
pcts = gender_pct_B,
colors = DONUT_B,
title = "Group B",
n = 36,
label_names= gender_labels)
# ─────────────────────────────────────────────
# BOTTOM LEGEND STRIP
# ─────────────────────────────────────────────
p1 = mpatches.Patch(color=COLOR_A_HIST, label="Group A — Test (n = 34)")
p2 = mpatches.Patch(color=COLOR_B_HIST, label="Group B — Control (n = 36)")
p3 = mpatches.Patch(color=DONUT_A[0], label="Female")
p4 = mpatches.Patch(color=DONUT_A[1], label="Male (Group A)")
p5 = mpatches.Patch(color=DONUT_B[0], label="Female")
p6 = mpatches.Patch(color=DONUT_B[1], label="Male (Group B)")
fig.legend(handles=[p1, p2, p3, p4, p5, p6],
loc="lower center", ncol=6,
fontsize=8.5, framealpha=0.95,
edgecolor="#AAAAAA", bbox_to_anchor=(0.5, 0.00),
handlelength=1.2, handleheight=0.9,
columnspacing=1.2)
# ─────────────────────────────────────────────
# SAVE
# ─────────────────────────────────────────────
plt.savefig("figure1_age_gender.png", dpi=DPI,
bbox_inches="tight", facecolor=FIGURE_BG)
plt.savefig("figure1_age_gender.svg",
bbox_inches="tight", facecolor=FIGURE_BG)
print("Saved: figure1_age_gender.png (high-res raster)")
print("Saved: figure1_age_gender.svg (vector — open in Inkscape / Adobe Illustrator)")
plt.show()
A wide clinical research figure titled "Figure 1: Age Distribution and Gender Distribution — Both Groups". Left panel (50% width): an overlapping histogram labeled "(A) Age Distribution" with vivid red semi-transparent bars for Group A (Test, n=34, mean=41.94±13.21 yrs) and steel blue semi-transparent bars for Group B (Control, n=36, mean=42.81±13.16 yrs), x-axis "Age (Years)" from 18 to 78, y-axis "Frequency", dashed vertical mean lines, dashed grid, white panel background, clean legend inside panel. Right side: two large donut charts side by side. Left donut "Group A (n=34)" in vivid red and light pink — Female 22 (64.7%) in deep red, Male 12 (35.3%) in light pink, labels outside wedges in bold matching colours, group title in centre. Right donut "Group B (n=36)" in forest green and mint green — Female 23 (63.9%) in forest green, Male 13 (36.1%) in mint green, labels outside, title in centre. Overall light grey figure background, dark navy bold title at top, italic subtitle "Mean age ≈ 42 years | Female predominance ≈ 64% | Both groups comparable (p > 0.05)", thin blue horizontal rule under subtitle, bottom legend strip with all colour patches labelled. High resolution, publication quality.

figure1_age_gender.py) are ready. Here's a quick guide:| Panel | Content |
|---|---|
| (A) Histogram | Overlapping age bars — red (Group A) + blue (Group B) with dashed mean lines |
| (B) Donut — Group A | Red/pink split: Female 22 (64.7%) / Male 12 (35.3%) |
| (C) Donut — Group B | Green/mint split: Female 23 (63.9%) / Male 13 (36.1%) |
| Variable | What it changes |
|---|---|
ages_A, ages_B | Individual age values — replace with your actual raw data |
mean_A, sd_A etc. | Stats shown in legend |
COLOR_A_HIST, COLOR_B_HIST | Histogram bar colours |
DONUT_A, DONUT_B | [Female colour, Male colour] for each donut |
BINS | Age bin edges for the histogram |
DPI = 180 | Output resolution (use 300 for print-quality) |
pip install matplotlib numpy
python figure1_age_gender.py
above 65age is exclusion according to this manage the image it showing 68 to 78 range show it up to 65 only
/home/user/figure1_age_gender.py
Successfully replaced 1 instance(s) of the string in '/home/user/figure1_age_gender.py'
/home/user/figure1_age_gender.py
Successfully replaced 1 instance(s) of the string in '/home/user/figure1_age_gender.py'
/home/user/figure1_age_gender.py
Successfully replaced 1 instance(s) of the string in '/home/user/figure1_age_gender.py'
/home/user/figure1_age_gender.py
Successfully replaced 1 instance(s) of the string in '/home/user/figure1_age_gender.py'
figure1_age_gender.py| What was changed | Before | After |
|---|---|---|
| Ages in data | Included 67, 68, 69 | Capped at 65 (exclusion criteria respected) |
| Histogram bins | [18, 28, 38, 48, 58, 68, 78] | [18, 28, 38, 48, 58, 66] |
| X-axis range | set_xlim(15, 80) | set_xlim(15, 68) — ends cleanly after 65 |
| X-axis tick labels | Went up to 70–80 | Now shows 18, 28, 38, 48, 58, 65 |
| Subtitle | No mention of age range | Now reads: "Age range: 18–65 years" |
python figure1_age_gender.py
Note on the image preview: Thegenerate_imagetool has reached its monthly plan limit. To get the updated rendered preview directly here, please upgrade your subscription or contact support. The Python script itself is fully corrected and will produce the right output when you run it locally.
Prakriti assessment was conducted using the standard Prakriti Pariksha methodology documented in Ashtanga Hridayam and the standardised Prakriti assessment tool. The distribution of dominant Prakriti types is presented in Table 1.3 and Figure 3. Vata-Pitta and Pitta-Kapha were the two dominant dual-Dosha Prakriti types in both groups. In Group A, Vata-Pitta Prakriti accounted for 10 participants (29.4%) and Pitta-Kapha for 10 participants (29.4%) — together comprising 58.8% of Group A. In Group B, Vata-Pitta and Pitta-Kapha were equally distributed at 11 participants each (30.6% each) — together comprising 61.2% of Group B. Vata-Kapha Prakriti accounted for 20.6% of Group A and 11.1% of Group B. Pure Vata, pure Pitta, and pure Kapha Prakriti types were less commonly represented in both groups. Sama Prakriti (balanced tri-dosha constitution) was observed in 2 participants (5.6%) in Group B and none in Group A. The predominance of dual-Dosha Prakriti types — particularly those involving Vata (Vata-Pitta and Vata-Kapha combined = 50.0% Group A; 41.7% Group B) — is clinically significant in the Ayurvedic framework. Dantaharsha is classified as a Vataja or Vata-Pittaja Danta Roga in classical texts (Sushruta Samhita NS 16/3; A.H. U. 22/13), and the predominance of Vata-dominant Prakriti types in the enrolled population supports the classical aetiological understanding of the condition. Both groups showed comparable Prakriti distributions (Chi-Square, p > 0.05). Table 1.4: Prakriti Distribution — Both Groups PRAKRITI TYPE GROUP A — n (%) GROUP B — n (%) Dominant Dosha p-value Vata-Pitta 10 (29.4%) 11 (30.6%) Vata + Pitta NS Pitta-Kapha 10 (29.4%) 11 (30.6%) Pitta + Kapha NS Vata-Kapha 7 (20.6%) 4 (11.1%) Vata + Kapha NS Pitta (Pure) 4 (11.8%) 1 (2.8%) Pitta NS Vata (Pure) 2 (5.9%) 4 (11.1%) Vata NS Kapha (Pure) 1 (2.9%) 3 (8.3%) Kapha NS Sama Prakriti (Balanced) 0 (0.0%) 2 (5.6%) Tri-Dosha balanced NS NS = Not Significant. Chi-Square test. Groups are comparable for Prakriti distribution (p > 0.05 for all comparisons). Vata-containing Prakriti = Vata-Pitta + Vata-Kapha + Vata (pure).short write up
Suggested closing line for Results: "The predominance of Vata-containing Prakriti types in both groups is noteworthy in the context of the Ayurvedic understanding of Dantaharsha and will be discussed further."
write results n observation for this
| Observation | Group A | Group B |
|---|---|---|
| Most common trigger | All Stimuli (26%) | Air Blast Only (33%) |
| Most common dietary worsener | Cold + Citrus (26%) | Citrus Fruits (28%) |
| No dietary worsener reported | 5 (15%) | 9 (25%) |
| Trigger profile | Broadly distributed | Evaporative-dominant |
please make results n observation part of this section but short keep only required tablesn graphs whichever is needed only keep proper sequence as per thesis phd protocol
| Domain | Best Parameter (Effect Size) | Group A Relief | Group B Relief | Between-Group Difference |
|---|---|---|---|---|
| Thermal & Taste | Sheeta (dz −2.49 / −1.97) | 70.1% | 70.5% | NS |
| Mechanical | Pravata (dz −2.61 / −2.21) | 73.1% | 74.0% | NS |
| Pain Quality | Ruk/Vedana (dz −1.94 / −1.43) | 67.6% | 66.3% | NS |
| Local Signs | Mukha Daha (dz −1.09 / −1.30) | 76.1% | 81.6% | NS |
| Item | Purpose | Keep? |
|---|---|---|
| Table 3.1 | Within-group D0 vs D30 per parameter | ✅ Essential |
| Table 3.2 | Between-group change score comparison | ✅ Essential |
| Figure 3.1 | D0 vs D30 bar chart — both groups | ✅ Essential |
| Figure 3.2 | Improvement score comparison | ✅ Essential |
| Figure 3.3 | % Domain-wise relief — both groups | ✅ Essential |
| Figure 3.4 | Cohen's dz effect size chart | ✅ Essential |
what does mean by dz
dz = Mean Difference (D0 − D30) ÷ Standard Deviation of the Differences
"How many standard deviations did the score change by?"
Pravata Asahishnuta (air blast sensitivity), Group A: dz = −2.61
prepare graphs accordingly
/home/user/section4_graphs.py
"""
SECTION 4 — Clinical Outcome Graphs
Comparative Clinical Trial: Yashtimadhvadi (Group A) vs RA Thermoseal (Group B)
Graphs included:
Figure 4.1 — OHI-S Mean Scores at D0, D15, D30 (Line + Bar combo)
Figure 4.2 — VAS Scores: Air Blast, Cold Water, Tactile (3-panel line chart)
Figure 4.3 — % Reduction Comparison — All VAS + Schiff parameters (grouped bar)
Figure 4.4 — Schiff Scores D0 vs D30 (grouped bar)
Figure 4.5 — EPT Readings D0, D15, D30 (line chart)
Figure 4.6 — Summary Outcome Dashboard (horizontal bar — % reduction)
Requirements: pip install matplotlib numpy
Run: python section4_graphs.py
"""
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
import numpy as np
# ══════════════════════════════════════════════
# GLOBAL STYLE
# ══════════════════════════════════════════════
COLOR_A = "#E63946" # vivid red — Group A (Yashtimadhvadi)
COLOR_B = "#457B9D" # steel blue — Group B (RA Thermoseal)
COLOR_A2 = "#FFAAB5" # light red (D30 accent)
COLOR_B2 = "#A8D5EA" # light blue (D30 accent)
MARKER_A = "o"
MARKER_B = "s"
FIGURE_BG = "#F8F9FA"
PANEL_BG = "#FFFFFF"
GRID_COLOR = "#DADADA"
FONT = "DejaVu Sans"
DPI = 180
DAYS = [0, 15, 30]
DAY_LABELS = ["D0\n(Baseline)", "D15\n(Mid)", "D30\n(End)"]
def style_ax(ax, title, xlabel="", ylabel="", ylim=None):
ax.set_facecolor(PANEL_BG)
ax.set_title(title, fontsize=11, fontweight="bold", color="#1D3557", pad=8)
if xlabel: ax.set_xlabel(xlabel, fontsize=9.5, color="#444")
if ylabel: ax.set_ylabel(ylabel, fontsize=9.5, color="#444")
ax.yaxis.grid(True, color=GRID_COLOR, linestyle="--", linewidth=0.7, zorder=0)
ax.set_axisbelow(True)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color("#CCCCCC")
ax.spines["bottom"].set_color("#CCCCCC")
ax.tick_params(labelsize=8.5)
if ylim: ax.set_ylim(ylim)
def sig_label(p):
if p < 0.001: return "***"
elif p < 0.01: return "**"
elif p < 0.05: return "*"
else: return "NS"
def save(fig, name):
fig.savefig(f"{name}.png", dpi=DPI, bbox_inches="tight", facecolor=FIGURE_BG)
fig.savefig(f"{name}.svg", bbox_inches="tight", facecolor=FIGURE_BG)
print(f"Saved: {name}.png / .svg")
plt.close(fig)
patch_A = mpatches.Patch(color=COLOR_A, label="Group A — Yashtimadhvadi (n=34)")
patch_B = mpatches.Patch(color=COLOR_B, label="Group B — RA Thermoseal (n=36)")
# ══════════════════════════════════════════════
# FIGURE 4.1 — OHI-S (Line Chart)
# ══════════════════════════════════════════════
ohis_A = [2.68, 2.15, 1.80]
ohis_B = [2.56, 2.25, 1.83]
ohis_se_A = [0.55/np.sqrt(34), 0.58/np.sqrt(34), 0.60/np.sqrt(34)]
ohis_se_B = [0.19/np.sqrt(36), 0.21/np.sqrt(36), 0.19/np.sqrt(36)]
fig, ax = plt.subplots(figsize=(7, 5), facecolor=FIGURE_BG)
ax.plot(DAYS, ohis_A, color=COLOR_A, marker=MARKER_A, linewidth=2.2,
markersize=8, label="Group A", zorder=3)
ax.plot(DAYS, ohis_B, color=COLOR_B, marker=MARKER_B, linewidth=2.2,
markersize=8, label="Group B", zorder=3)
ax.fill_between(DAYS,
[v-e for v,e in zip(ohis_A,ohis_se_A)],
[v+e for v,e in zip(ohis_A,ohis_se_A)],
color=COLOR_A, alpha=0.12)
ax.fill_between(DAYS,
[v-e for v,e in zip(ohis_B,ohis_se_B)],
[v+e for v,e in zip(ohis_B,ohis_se_B)],
color=COLOR_B, alpha=0.12)
for i,(da,db) in enumerate(zip(ohis_A,ohis_B)):
ax.text(DAYS[i], da+0.06, f"{da}", ha="center", fontsize=8.5,
color=COLOR_A, fontweight="bold")
ax.text(DAYS[i], db-0.12, f"{db}", ha="center", fontsize=8.5,
color=COLOR_B, fontweight="bold")
ax.set_xticks(DAYS); ax.set_xticklabels(DAY_LABELS)
ax.set_ylim(1.2, 3.2)
# inter-group annotation
ax.annotate("NS at all\ntime points", xy=(15,2.20), fontsize=8,
color="#555", fontstyle="italic", ha="center")
ax.annotate("p<0.001***\n(intra-group)", xy=(28,1.55), fontsize=8,
color=COLOR_A, ha="right")
style_ax(ax, "Figure 4.1: OHI-S Mean Scores — D0, D15, D30",
"Time Point", "OHI-S Mean Score")
ax.legend(handles=[patch_A, patch_B], fontsize=8.5, framealpha=0.9,
edgecolor="#CCC", loc="upper right")
fig.text(0.5, 0.01,
"Both groups: intra-group p < 0.001 (Wilcoxon). Inter-group NS at all time points.",
ha="center", fontsize=8, color="#555", fontstyle="italic")
plt.tight_layout(rect=[0,0.04,1,1])
save(fig, "fig4_1_ohis")
# ══════════════════════════════════════════════
# FIGURE 4.2 — VAS Scores (3-panel line chart)
# ══════════════════════════════════════════════
vas_data = {
"Air Blast VAS\n(0–10 scale)": {
"A": [6.84, 3.62, 1.39], "B": [6.92, 3.48, 2.04],
"seA":[0.92/np.sqrt(34),0.57/np.sqrt(34),0.38/np.sqrt(34)],
"seB":[0.28/np.sqrt(36),0.28/np.sqrt(36),0.11/np.sqrt(36)],
"d30_p":"p<0.001***", "pct_A":"79.7%", "pct_B":"70.4%"
},
"Cold Water VAS\n(0–10 scale)": {
"A": [6.41, 3.50, 1.38], "B": [6.67, 3.33, 2.02],
"seA":[0.86/np.sqrt(34),0.58/np.sqrt(34),0.32/np.sqrt(34)],
"seB":[0.30/np.sqrt(36),0.35/np.sqrt(36),0.15/np.sqrt(36)],
"d30_p":"p<0.001***", "pct_A":"78.2%", "pct_B":"69.6%"
},
"Tactile VAS\n(0–10 scale)": {
"A": [6.07, None, 1.18], "B": [6.37, None, 1.89],
"seA":[0.90/np.sqrt(34), None, 0.25/np.sqrt(34)],
"seB":[0.31/np.sqrt(36), None, 0.13/np.sqrt(36)],
"d30_p":"p<0.001***", "pct_A":"80.4%", "pct_B":"70.2%"
},
}
fig, axes = plt.subplots(1, 3, figsize=(15, 5.5), facecolor=FIGURE_BG)
fig.suptitle("Figure 4.2: VAS Sensitivity Scores — D0, D15, D30\nGroup A (Yashtimadhvadi) vs Group B (RA Thermoseal)",
fontsize=12, fontweight="bold", color="#1D3557", y=1.01)
for ax, (title, d) in zip(axes, vas_data.items()):
dA = [v for v in d["A"] if v is not None]
dB = [v for v in d["B"] if v is not None]
tA = [DAYS[i] for i,v in enumerate(d["A"]) if v is not None]
tB = [DAYS[i] for i,v in enumerate(d["B"]) if v is not None]
seA = [v for v in d["seA"] if v is not None]
seB = [v for v in d["seB"] if v is not None]
ax.plot(tA, dA, color=COLOR_A, marker=MARKER_A, linewidth=2.2,
markersize=8, label="Group A", zorder=3)
ax.plot(tB, dB, color=COLOR_B, marker=MARKER_B, linewidth=2.2,
markersize=8, label="Group B", zorder=3)
ax.fill_between(tA,[v-e for v,e in zip(dA,seA)],[v+e for v,e in zip(dA,seA)],
color=COLOR_A, alpha=0.12)
ax.fill_between(tB,[v-e for v,e in zip(dB,seB)],[v+e for v,e in zip(dB,seB)],
color=COLOR_B, alpha=0.12)
for t,va,vb in zip(tA,dA,dB):
ax.text(t, va+0.2, f"{va}", ha="center", fontsize=8, color=COLOR_A, fontweight="bold")
ax.text(t, vb-0.45, f"{vb}", ha="center", fontsize=8, color=COLOR_B, fontweight="bold")
# D30 significance bracket
y_br = max(dA[-1],dB[-1]) + 0.5
ax.annotate("", xy=(30, dA[-1]+0.1), xytext=(30, dB[-1]+0.1),
arrowprops=dict(arrowstyle="-", color="#333", lw=1.2))
ax.text(30.6, (dA[-1]+dB[-1])/2, d["d30_p"],
fontsize=7.5, color="#333", va="center")
# % reduction box
ax.text(0.02, 0.18,
f"% Reduction D0→D30\nGroup A: {d['pct_A']}\nGroup B: {d['pct_B']}",
transform=ax.transAxes, fontsize=7.5,
bbox=dict(boxstyle="round,pad=0.3", facecolor="#FFF9C4",
edgecolor="#CCAA00", alpha=0.9))
ax.set_xticks([t for t in tA])
ax.set_xticklabels([DAY_LABELS[DAYS.index(t)] for t in tA])
ax.set_ylim(0, 8.5)
style_ax(ax, title, "Time Point", "VAS Score (0–10)")
ax.legend(fontsize=7.5, framealpha=0.9, edgecolor="#CCC", loc="upper right")
plt.tight_layout()
save(fig, "fig4_2_vas")
# ══════════════════════════════════════════════
# FIGURE 4.3 — % Reduction Summary (Grouped Bar)
# ══════════════════════════════════════════════
params = ["Air Blast\nVAS", "Cold Water\nVAS", "Tactile\nVAS",
"Air Blast\nSchiff", "Cold Water\nSchiff", "OHI-S",
"EPT\n(% increase)"]
pct_A = [79.7, 78.2, 80.4, 92.0, 89.7, 32.8, 44.5]
pct_B = [70.4, 69.6, 70.2, 75.3, 77.4, 28.5, 46.4]
p_vals = ["<0.001", "<0.001", "<0.001", "0.016", "0.111 NS", "0.377 NS", "0.017"]
favours = ["A","A","A","A","—","—","B"]
x = np.arange(len(params))
BAR_W = 0.32
fig, ax = plt.subplots(figsize=(14, 6.5), facecolor=FIGURE_BG)
bA = ax.bar(x - BAR_W/2, pct_A, BAR_W, color=COLOR_A, edgecolor="white",
linewidth=0.8, label="Group A", zorder=3)
bB = ax.bar(x + BAR_W/2, pct_B, BAR_W, color=COLOR_B, edgecolor="white",
linewidth=0.8, label="Group B", zorder=3)
for bar in bA:
h = bar.get_height()
ax.text(bar.get_x()+bar.get_width()/2, h+0.8, f"{h}%",
ha="center", fontsize=8, fontweight="bold", color=COLOR_A)
for bar in bB:
h = bar.get_height()
ax.text(bar.get_x()+bar.get_width()/2, h+0.8, f"{h}%",
ha="center", fontsize=8, fontweight="bold", color=COLOR_B)
# p-value + favours annotation
for i,(p,f) in enumerate(zip(p_vals, favours)):
col = "#C00" if "NS" not in p else "#888"
ax.text(x[i], max(pct_A[i],pct_B[i])+5.5,
f"p={p}", ha="center", fontsize=7.5, color=col, fontstyle="italic")
if f != "—":
fc = COLOR_A if f=="A" else COLOR_B
ax.text(x[i], max(pct_A[i],pct_B[i])+10,
f"▲ Grp {f}", ha="center", fontsize=7.5, color=fc, fontweight="bold")
ax.set_xticks(x); ax.set_xticklabels(params, fontsize=9)
ax.set_ylim(0, 110)
ax.axhline(50, color="#AAAAAA", linestyle=":", linewidth=1.0)
ax.text(6.6, 51.5, "50%\nthreshold", fontsize=7.5, color="#888")
style_ax(ax, "Figure 4.3: % Reduction/Improvement D0→D30 — All Outcome Parameters",
"", "Mean % Reduction / Improvement")
ax.legend(handles=[patch_A, patch_B], fontsize=9, framealpha=0.9,
edgecolor="#CCC", loc="upper left")
fig.text(0.5, 0.01,
"All intra-group comparisons p < 0.001 (Wilcoxon). ▲ = statistically superior group at D30.",
ha="center", fontsize=8, color="#555", fontstyle="italic")
plt.tight_layout(rect=[0,0.04,1,1])
save(fig, "fig4_3_pct_reduction")
# ══════════════════════════════════════════════
# FIGURE 4.4 — Schiff Scores D0 vs D30
# ══════════════════════════════════════════════
schiff_params = ["Air Blast\nSchiff", "Cold Water\nSchiff"]
d0_A = [2.59, 2.35]; d30_A = [0.24, 0.31]
d0_B = [2.25, 2.42]; d30_B = [0.55, 0.52]
pct_redA = [92.0, 89.7]; pct_redB = [75.3, 77.4]
schiff_p = ["p=0.016*", "p=0.111 NS"]
x = np.arange(len(schiff_params))
W = 0.20
fig, ax = plt.subplots(figsize=(8, 5.5), facecolor=FIGURE_BG)
b1 = ax.bar(x - 1.5*W, d0_A, W, color=COLOR_A, alpha=0.5, edgecolor="white", label="Group A — D0")
b2 = ax.bar(x - 0.5*W, d30_A, W, color=COLOR_A, alpha=1.0, edgecolor="white", label="Group A — D30")
b3 = ax.bar(x + 0.5*W, d0_B, W, color=COLOR_B, alpha=0.5, edgecolor="white", label="Group B — D0")
b4 = ax.bar(x + 1.5*W, d30_B, W, color=COLOR_B, alpha=1.0, edgecolor="white", label="Group B — D30")
for bars in [b1,b2,b3,b4]:
for bar in bars:
h = bar.get_height()
ax.text(bar.get_x()+bar.get_width()/2, h+0.03, f"{h}",
ha="center", fontsize=8, fontweight="bold",
color="#333")
# % reduction labels
for i in range(len(schiff_params)):
ax.text(x[i]-W, d0_A[i]+0.18,
f"↓{pct_redA[i]}%", ha="center", fontsize=8,
color=COLOR_A, fontweight="bold")
ax.text(x[i]+W, d0_B[i]+0.18,
f"↓{pct_redB[i]}%", ha="center", fontsize=8,
color=COLOR_B, fontweight="bold")
ax.text(x[i], 2.8, schiff_p[i], ha="center", fontsize=8.5,
color="#C00" if "*" in schiff_p[i] else "#888",
fontweight="bold")
ax.set_xticks(x); ax.set_xticklabels(schiff_params, fontsize=10)
ax.set_ylim(0, 3.2)
ax.set_yticks([0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0])
ax.set_yticklabels(["0\n(Absent)", "0.5", "1.0\n(Mild)", "1.5",
"2.0\n(Moderate)", "2.5", "3.0\n(Severe)"], fontsize=7.5)
style_ax(ax, "Figure 4.4: Schiff Sensitivity Scores — D0 vs D30",
"Stimulus Type", "Schiff Score (0–3)")
ax.legend(fontsize=8, framealpha=0.9, edgecolor="#CCC",
loc="upper right", ncol=2)
fig.text(0.5, 0.01,
"Both groups: intra-group p < 0.001. ↓% = % reduction. Inter-group p shown above bars.",
ha="center", fontsize=8, color="#555", fontstyle="italic")
plt.tight_layout(rect=[0,0.04,1,1])
save(fig, "fig4_4_schiff")
# ══════════════════════════════════════════════
# FIGURE 4.5 — EPT Readings (Line Chart)
# ══════════════════════════════════════════════
ept_A = [17.96, 24.98, 32.28]
ept_B = [18.10, 24.64, 33.95]
ept_seA = [0.82/np.sqrt(34), 1.53/np.sqrt(34), 1.68/np.sqrt(34)]
ept_seB = [1.19/np.sqrt(36), 1.78/np.sqrt(36), 2.31/np.sqrt(36)]
fig, ax = plt.subplots(figsize=(7, 5), facecolor=FIGURE_BG)
ax.plot(DAYS, ept_A, color=COLOR_A, marker=MARKER_A, linewidth=2.3,
markersize=9, label="Group A — Yashtimadhvadi", zorder=3)
ax.plot(DAYS, ept_B, color=COLOR_B, marker=MARKER_B, linewidth=2.3,
markersize=9, label="Group B — RA Thermoseal", zorder=3)
ax.fill_between(DAYS,
[v-e for v,e in zip(ept_A,ept_seA)],
[v+e for v,e in zip(ept_A,ept_seA)],
color=COLOR_A, alpha=0.12)
ax.fill_between(DAYS,
[v-e for v,e in zip(ept_B,ept_seB)],
[v+e for v,e in zip(ept_B,ept_seB)],
color=COLOR_B, alpha=0.12)
for t,va,vb in zip(DAYS,ept_A,ept_B):
ax.text(t, va+0.6, f"{va}", ha="center", fontsize=8.5,
color=COLOR_A, fontweight="bold")
ax.text(t, vb-1.4, f"{vb}", ha="center", fontsize=8.5,
color=COLOR_B, fontweight="bold")
# D30 inter-group annotation
ax.annotate("p=0.005**\n(D30 inter-group;\nGroup B marginally higher)",
xy=(30, 33.0), xytext=(22, 35.5),
arrowprops=dict(arrowstyle="->", color="#333", lw=1.2),
fontsize=8, color="#333",
bbox=dict(boxstyle="round,pad=0.3",
facecolor="#EEF4FB", edgecolor="#457B9D", alpha=0.9))
ax.axhline(40, color="#DDD", linestyle=":", linewidth=1)
ax.text(0.5, 41, "Normal upper limit ~80 µA", fontsize=7.5, color="#AAA")
ax.set_xticks(DAYS); ax.set_xticklabels(DAY_LABELS)
ax.set_ylim(10, 45)
style_ax(ax, "Figure 4.5: Electric Pulp Test (EPT) Readings — D0, D15, D30",
"Time Point", "EPT Reading (µA) [Higher = Better]")
ax.legend(fontsize=8.5, framealpha=0.9, edgecolor="#CCC", loc="upper left")
fig.text(0.5, 0.01,
"Both groups: intra-group p < 0.001. Higher µA = reduced pulp reactivity = desensitisation.\n"
"Group A: +44.5% | Group B: +46.4% | Inter-group p = 0.017 (marginal favour Group B).",
ha="center", fontsize=8, color="#555", fontstyle="italic")
plt.tight_layout(rect=[0,0.05,1,1])
save(fig, "fig4_5_ept")
# ══════════════════════════════════════════════
# FIGURE 4.6 — Summary Dashboard (Horizontal Bar)
# ══════════════════════════════════════════════
params_h = ["Air Blast VAS", "Cold Water VAS", "Tactile VAS",
"Air Blast Schiff", "Cold Water Schiff", "OHI-S",
"EPT (% increase)"]
pct_A_h = [79.7, 78.2, 80.4, 92.0, 89.7, 32.8, 44.5]
pct_B_h = [70.4, 69.6, 70.2, 75.3, 77.4, 28.5, 46.4]
sig_h = ["***","***","***","*","NS","NS","*"]
fav_h = ["A","A","A","A","—","—","B"]
y = np.arange(len(params_h))
H = 0.32
fig, ax = plt.subplots(figsize=(11, 6), facecolor=FIGURE_BG)
bA = ax.barh(y + H/2, pct_A_h, H, color=COLOR_A, edgecolor="white",
linewidth=0.8, label="Group A", zorder=3)
bB = ax.barh(y - H/2, pct_B_h, H, color=COLOR_B, edgecolor="white",
linewidth=0.8, label="Group B", zorder=3)
for bar, val, s, f in zip(bA, pct_A_h, sig_h, fav_h):
ax.text(val+0.5, bar.get_y()+bar.get_height()/2,
f"{val}%", va="center", fontsize=8.5,
fontweight="bold", color=COLOR_A)
for bar, val in zip(bB, pct_B_h):
ax.text(val+0.5, bar.get_y()+bar.get_height()/2,
f"{val}%", va="center", fontsize=8.5,
fontweight="bold", color=COLOR_B)
# significance markers
for i,(s,f) in enumerate(zip(sig_h,fav_h)):
mx = max(pct_A_h[i], pct_B_h[i])
col = "#C00" if s!="NS" else "#888"
ax.text(mx+6, i, f"p {s}", va="center", fontsize=8,
color=col, fontstyle="italic")
if f != "—":
fc = COLOR_A if f=="A" else COLOR_B
ax.text(mx+13, i, f"▲ Grp {f}", va="center",
fontsize=8, color=fc, fontweight="bold")
ax.axvline(50, color="#AAAAAA", linestyle=":", linewidth=1.0)
ax.text(51, -0.6, "50%", fontsize=8, color="#888")
ax.set_yticks(y); ax.set_yticklabels(params_h, fontsize=10)
ax.set_xlim(0, 108)
ax.set_xlabel("Mean % Reduction / Improvement (D0 → D30)", fontsize=10, color="#444")
style_ax(ax, "Figure 4.6: Summary — % Outcome Improvement D0→D30\nGroup A vs Group B")
ax.legend(handles=[patch_A, patch_B], fontsize=9, framealpha=0.9,
edgecolor="#CCC", loc="lower right")
fig.text(0.5, 0.01,
"All intra-group comparisons p < 0.001 (Wilcoxon). ▲ = statistically superior group at D30.",
ha="center", fontsize=8, color="#555", fontstyle="italic")
plt.tight_layout(rect=[0,0.04,1,1])
save(fig, "fig4_6_summary_dashboard")
print("\n✓ All 6 figures saved (PNG + SVG).")
print("Files: fig4_1_ohis | fig4_2_vas | fig4_3_pct_reduction |"
" fig4_4_schiff | fig4_5_ept | fig4_6_summary_dashboard")
| Figure | Type | What it shows |
|---|---|---|
| Fig 4.1 | Line chart | OHI-S scores at D0, D15, D30 — both groups |
| Fig 4.2 | 3-panel line chart | VAS scores (Air Blast / Cold Water / Tactile) across time points |
| Fig 4.3 | Grouped bar | % Reduction for ALL outcome parameters side by side |
| Fig 4.4 | Grouped bar | Schiff scores D0 vs D30 with % reduction arrows |
| Fig 4.5 | Line chart | EPT readings (µA) progression D0→D15→D30 |
| Fig 4.6 | Horizontal bar dashboard | Summary of all % outcomes — best for thesis overview page |
pip install matplotlib numpy
python section4_graphs.py
Image preview is unavailable as the monthlygenerate_imagelimit has been reached. Please upgrade your subscription to get rendered previews directly in chat. The script is complete and will produce all figures correctly when run locally.
SEM Tubule Occlusion: PRE vs POST (Ordinal Scale) — Non-parametric Analysis Outcome is an ordinal 5-point scoring scale. Therefore, non-parametric tests are used throughout: Wilcoxon signed-rank for within-group paired PRE vs POST, and Mann–Whitney U for between-group comparisons. Effect sizes are reported as r (Wilcoxon) and rank-biserial correlation r for Mann–Whitney. Ten paired observations per group were matched by position (Tooth 1 to 10). Table 1. Extracted PRE and POST Scores (by Tooth) Tooth Group1_PRE Group1_POST Group2_PRE Group2_POST 1.0 4.6 2.0 4.8 2.8 2.0 4.45 1.9 4.65 2.3 3.0 4.65 2.1 4.5 2.9 4.0 4.7 2.4 4.3 2.3 5.0 4.25 2.0 4.45 2.3 6.0 4.6 2.1 4.8 2.3 7.0 4.75 2.4 4.6 2.8 8.0 4.7 2.1 4.5 2.5 9.0 4.6 2.2 4.3 2.7 10.0 4.25 2.1 4.45 2.9 Table 2. Change Scores (PRE−POST) Tooth Group1_Change_PRE-POST Group2_Change_PRE-POST 1.0 2.6 2.0 2.0 2.55 2.35 3.0 2.55 1.6 4.0 2.3 2.0 5.0 2.25 2.15 6.0 2.5 2.5 7.0 2.35 1.8 8.0 2.6 2.0 9.0 2.4 1.6 10.0 2.15 1.55 Table 3. Within-group PRE vs POST (Wilcoxon signed-rank) Group n PRE median (IQR) POST median (IQR) Wilcoxon W p-value Effect size r Group 1 (Potassium Nitrate) 10 4.6 (4.487–4. 688) 2.1 (2.025–2. 175) 0.0 0.001953 0.886 Group 2 (Ayurvedic ) 10 4.5 (4.45–4.6 38) 2.6 (2.3–2.8) 0.0 0.001953 0.886 Table 4. Between-group Comparisons (Mann–Whitney U) Comparison Test n1 n2 Group 1 median (IQR) Group 2 median (IQR) U p-value Effect size (rank- biserial r) POST scores: Group 1 vs Group 2 Mann– Whitne y U 10 10 2.1 (2.025 –2.175 ) 2.6 (2.3–2. 8) 8.0 0.00154 7 0.84 Change (PRE−POST) : Group 1 vs Group 2 Mann– Whitne y U 10 10 2.45 (2.312 –2.55) 2.0 (1.65– 2.113) 89.0 0.00356 -0.78 Interpretation (brief) Within-group: Both Group 1 and Group 2 show statistically significant reductions in scores from PRE to POST (Wilcoxon p < 0.01), indicating improved tubule occlusion after treatment in both groups. Between-group: POST scores are significantly lower in Group 1 than Group 2, suggesting superior final occlusion in Group 1. The change score (PRE−POST) also differs significantly between groups, consistent with a larger improvement in Group 1 for the extracted ranges.provide me writeup for sem and tables n images of comparison results n observations
/home/user/sem_graphs.py
"""
SEM Tubule Occlusion — PRE vs POST Graphs
Group 1 = Potassium Nitrate (KNO₃) | Group 2 = Ayurvedic (Yashtimadhvadi)
n = 10 per group (paired observations, Tooth 1–10)
Figures:
Fig SEM-1 : PRE vs POST scores per tooth — both groups (line plot)
Fig SEM-2 : PRE vs POST median box-plots — both groups (2-panel)
Fig SEM-3 : Change score (PRE−POST) per tooth — grouped bar
Fig SEM-4 : POST score comparison — Group 1 vs Group 2 (box + strip)
Fig SEM-5 : Summary effect-size & p-value panel
Requirements: pip install matplotlib numpy scipy
Run: python sem_graphs.py
"""
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.lines as mlines
import numpy as np
from matplotlib.patches import FancyBboxPatch
# ── COLOURS ──────────────────────────────────
C1 = "#E63946" # Group 1 — KNO₃ (red)
C2 = "#457B9D" # Group 2 — Ayurvedic (blue)
C1L = "#FFAAB5"
C2L = "#A8D5EA"
FIG_BG = "#F8F9FA"
PAN_BG = "#FFFFFF"
GRID = "#DADADA"
FONT = "DejaVu Sans"
DPI = 180
# ── DATA ─────────────────────────────────────
teeth = np.arange(1, 11)
g1_pre = np.array([4.60, 4.45, 4.65, 4.70, 4.25, 4.60, 4.75, 4.70, 4.60, 4.25])
g1_post = np.array([2.00, 1.90, 2.10, 2.40, 2.00, 2.10, 2.40, 2.10, 2.20, 2.10])
g2_pre = np.array([4.80, 4.65, 4.50, 4.30, 4.45, 4.80, 4.60, 4.50, 4.30, 4.45])
g2_post = np.array([2.80, 2.30, 2.90, 2.30, 2.30, 2.30, 2.80, 2.50, 2.70, 2.90])
g1_chg = g1_pre - g1_post
g2_chg = g2_pre - g2_post
def style(ax, title, xl="", yl="", ylim=None):
ax.set_facecolor(PAN_BG)
ax.set_title(title, fontsize=10.5, fontweight="bold",
color="#1D3557", pad=7)
if xl: ax.set_xlabel(xl, fontsize=9, color="#444")
if yl: ax.set_ylabel(yl, fontsize=9, color="#444")
ax.yaxis.grid(True, color=GRID, linestyle="--", linewidth=0.7, zorder=0)
ax.set_axisbelow(True)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color("#CCCCCC")
ax.spines["bottom"].set_color("#CCCCCC")
ax.tick_params(labelsize=8.5)
if ylim: ax.set_ylim(ylim)
def save(fig, name):
fig.savefig(f"{name}.png", dpi=DPI, bbox_inches="tight", facecolor=FIG_BG)
fig.savefig(f"{name}.svg", bbox_inches="tight", facecolor=FIG_BG)
print(f" Saved: {name}.png / .svg")
plt.close(fig)
patch1 = mpatches.Patch(color=C1, label="Group 1 — KNO₃ (Potassium Nitrate)")
patch2 = mpatches.Patch(color=C2, label="Group 2 — Ayurvedic (Yashtimadhvadi)")
# ═══════════════════════════════════════════════════════
# FIG SEM-1 : PRE vs POST per tooth — line plot (2-panel)
# ═══════════════════════════════════════════════════════
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5.5), facecolor=FIG_BG)
fig.suptitle("Figure SEM-1: SEM Tubule Occlusion Score — PRE vs POST by Tooth\n"
"Group 1 (KNO₃) and Group 2 (Ayurvedic) | n = 10 per group",
fontsize=12, fontweight="bold", color="#1D3557", y=1.01)
for ax, pre, post, c, cl, grp in [
(ax1, g1_pre, g1_post, C1, C1L, "Group 1 — KNO₃ (Potassium Nitrate)"),
(ax2, g2_pre, g2_post, C2, C2L, "Group 2 — Ayurvedic (Yashtimadhvadi)")]:
ax.fill_between(teeth, pre, post, alpha=0.12, color=c, label="Reduction area")
ax.plot(teeth, pre, color=c, marker="o", linewidth=2.0, markersize=7,
linestyle="--", alpha=0.7, label="PRE")
ax.plot(teeth, post, color=c, marker="s", linewidth=2.2, markersize=7,
label="POST")
for t, pr, po in zip(teeth, pre, post):
ax.text(t, pr+0.04, f"{pr}", ha="center", fontsize=7.5,
color="#666")
ax.text(t, po-0.12, f"{po}", ha="center", fontsize=7.5,
color=c, fontweight="bold")
ax.set_xticks(teeth)
ax.set_xticklabels([f"T{i}" for i in teeth])
ax.set_ylim(1.2, 5.3)
ax.axhline(np.median(pre), color=c, linestyle=":", linewidth=1.0, alpha=0.5)
ax.axhline(np.median(post), color=c, linestyle=":", linewidth=1.0, alpha=0.5)
ax.text(10.3, np.median(pre), f"PRE\nmedian\n{np.median(pre):.2f}",
va="center", fontsize=7.5, color="#888")
ax.text(10.3, np.median(post), f"POST\nmedian\n{np.median(post):.2f}",
va="center", fontsize=7.5, color=c, fontweight="bold")
style(ax, grp, "Tooth Number", "Occlusion Score (0–5 ordinal scale)", ylim=(1.0, 5.5))
ax.legend(fontsize=8, framealpha=0.9, edgecolor="#CCC", loc="upper right")
ax.text(0.02, 0.10,
"Wilcoxon p = 0.0020\nEffect size r = 0.886",
transform=ax.transAxes, fontsize=8,
bbox=dict(boxstyle="round,pad=0.3",
facecolor="#FFF9C4", edgecolor="#CCAA00", alpha=0.9))
plt.tight_layout()
save(fig, "fig_sem1_pre_post_tooth")
# ═══════════════════════════════════════════════════════
# FIG SEM-2 : Box plots — PRE & POST, both groups
# ═══════════════════════════════════════════════════════
fig, axes = plt.subplots(1, 2, figsize=(11, 6), facecolor=FIG_BG)
fig.suptitle("Figure SEM-2: Box Plots — PRE vs POST Occlusion Scores\n"
"Both groups | Wilcoxon p < 0.01 (within-group)",
fontsize=12, fontweight="bold", color="#1D3557", y=1.01)
for ax, pre, post, c, grp, post_med, chg_med in [
(axes[0], g1_pre, g1_post, C1,
"Group 1 — KNO₃", "2.10", "2.45"),
(axes[1], g2_pre, g2_post, C2,
"Group 2 — Ayurvedic", "2.60", "2.00")]:
bp = ax.boxplot([pre, post],
positions=[1, 2],
widths=0.45,
patch_artist=True,
medianprops=dict(color="white", linewidth=2.5),
whiskerprops=dict(color=c, linewidth=1.5),
capprops=dict(color=c, linewidth=1.5),
flierprops=dict(marker="o", color=c, alpha=0.5))
colors_bp = [c + "55", c] # hex alpha trick — light for PRE, solid for POST
fills = ["#F5A0A8" if c==C1 else "#A0C4DA", c]
for patch, fc in zip(bp["boxes"], fills):
patch.set_facecolor(fc)
patch.set_alpha(0.85)
# jitter strip
np.random.seed(42)
for vals, pos in [(pre, 1), (post, 2)]:
jitter = np.random.uniform(-0.08, 0.08, len(vals))
ax.scatter(np.full(len(vals), pos) + jitter, vals,
color=c, alpha=0.7, s=40, zorder=5)
# connect paired observations
for pr, po in zip(pre, post):
ax.plot([1, 2], [pr, po], color="#888888", linewidth=0.7,
alpha=0.4, zorder=2)
# significance bracket
y_br = 5.1
ax.plot([1, 1, 2, 2], [y_br-0.05, y_br, y_br, y_br-0.05],
color="#333", linewidth=1.2)
ax.text(1.5, y_br+0.05, "p = 0.002 **\nr = 0.886 (Large)",
ha="center", fontsize=8.5, color="#C00", fontweight="bold")
ax.set_xticks([1, 2])
ax.set_xticklabels(["PRE\n(Baseline)", "POST\n(Day 30)"], fontsize=9.5)
style(ax, grp, "", "Occlusion Score (0–5)", ylim=(1.0, 5.6))
plt.tight_layout()
save(fig, "fig_sem2_boxplots")
# ═══════════════════════════════════════════════════════
# FIG SEM-3 : Change scores per tooth — grouped bar
# ═══════════════════════════════════════════════════════
x = np.arange(len(teeth))
BW = 0.35
fig, ax = plt.subplots(figsize=(12, 5.5), facecolor=FIG_BG)
b1 = ax.bar(x - BW/2, g1_chg, BW, color=C1, edgecolor="white",
linewidth=0.8, label="Group 1 — KNO₃", zorder=3)
b2 = ax.bar(x + BW/2, g2_chg, BW, color=C2, edgecolor="white",
linewidth=0.8, label="Group 2 — Ayurvedic", zorder=3)
for bar, val in [(b, v) for bars,vals in [(b1,g1_chg),(b2,g2_chg)]
for bar,val in zip(bars,vals)]:
ax.text(bar.get_x()+bar.get_width()/2, val+0.04,
f"{val:.2f}", ha="center", fontsize=8,
fontweight="bold",
color=C1 if bar in b1 else C2)
ax.axhline(np.median(g1_chg), color=C1, linestyle="--", linewidth=1.4,
alpha=0.7, label=f"Group 1 median change = {np.median(g1_chg):.2f}")
ax.axhline(np.median(g2_chg), color=C2, linestyle="--", linewidth=1.4,
alpha=0.7, label=f"Group 2 median change = {np.median(g2_chg):.2f}")
ax.text(9.6, np.median(g1_chg)+0.03, f"{np.median(g1_chg):.2f}",
fontsize=8.5, color=C1, fontweight="bold")
ax.text(9.6, np.median(g2_chg)-0.08, f"{np.median(g2_chg):.2f}",
fontsize=8.5, color=C2, fontweight="bold")
ax.text(0.98, 0.92,
"Mann–Whitney U (change scores)\np = 0.004 | r = −0.78\nGroup 1 > Group 2 (larger change)",
transform=ax.transAxes, fontsize=8.5, ha="right",
bbox=dict(boxstyle="round,pad=0.4", facecolor="#FFF9C4",
edgecolor="#CCAA00", alpha=0.95))
ax.set_xticks(x)
ax.set_xticklabels([f"Tooth {i}" for i in teeth], fontsize=8.5)
ax.set_ylim(0, 3.5)
style(ax, "Figure SEM-3: Change Scores (PRE − POST) per Tooth — Group 1 vs Group 2",
"Tooth Number", "Score Change (PRE − POST) [Higher = More Occlusion]")
ax.legend(fontsize=8.5, framealpha=0.9, edgecolor="#CCC", loc="upper left", ncol=2)
fig.text(0.5, 0.01,
"Higher bar = greater tubule occlusion improvement. "
"Group 1 median change (2.45) > Group 2 (2.00) — p = 0.004 (Mann–Whitney).",
ha="center", fontsize=8, color="#555", fontstyle="italic")
plt.tight_layout(rect=[0, 0.04, 1, 1])
save(fig, "fig_sem3_change_scores")
# ═══════════════════════════════════════════════════════
# FIG SEM-4 : POST score comparison — box + strip
# ═══════════════════════════════════════════════════════
fig, ax = plt.subplots(figsize=(7, 6), facecolor=FIG_BG)
bp = ax.boxplot([g1_post, g2_post],
positions=[1, 2],
widths=0.5,
patch_artist=True,
medianprops=dict(color="white", linewidth=2.5),
whiskerprops=dict(linewidth=1.5),
capprops=dict(linewidth=1.5))
fills = [C1, C2]
for patch, fc, wc, cc in zip(bp["boxes"], fills,
[C1,C2],[C1,C2]):
patch.set_facecolor(fc)
patch.set_alpha(0.82)
for whisker, cap, color in zip(
bp["whiskers"], bp["caps"],
[C1,C1,C2,C2,C1,C1,C2,C2]):
whisker.set_color(color)
cap.set_color(color)
np.random.seed(0)
for vals, pos, c in [(g1_post, 1, C1), (g2_post, 2, C2)]:
jitter = np.random.uniform(-0.09, 0.09, len(vals))
ax.scatter(np.full(len(vals), pos)+jitter, vals,
color=c, s=55, zorder=5, edgecolors="white", linewidth=0.7)
# significance bracket
y_br = 3.25
ax.plot([1,1,2,2],[y_br-0.05,y_br,y_br,y_br-0.05],
color="#333", linewidth=1.3)
ax.text(1.5, y_br+0.05,
"POST: p = 0.0015**\nMann–Whitney U = 8\nRank-biserial r = 0.84",
ha="center", fontsize=9, color="#C00", fontweight="bold")
# median labels
ax.text(1, np.median(g1_post)-0.12, f"Median\n{np.median(g1_post):.2f}",
ha="center", fontsize=9, color="white", fontweight="bold")
ax.text(2, np.median(g2_post)-0.12, f"Median\n{np.median(g2_post):.2f}",
ha="center", fontsize=9, color="white", fontweight="bold")
ax.set_xticks([1,2])
ax.set_xticklabels(["Group 1\nKNO₃\n(n=10)",
"Group 2\nAyurvedic\n(n=10)"], fontsize=10)
ax.set_ylim(1.5, 3.7)
style(ax,
"Figure SEM-4: POST Scores — Group 1 vs Group 2\n(Between-Group Comparison)",
"", "POST Occlusion Score (0–5 ordinal)")
ax.text(0.02, 0.05,
"Lower POST score = Better tubule occlusion\n"
"Group 1 (KNO₃) significantly lower than Group 2",
transform=ax.transAxes, fontsize=8.5,
bbox=dict(boxstyle="round,pad=0.3",
facecolor="#EEF4FB", edgecolor="#457B9D", alpha=0.9))
plt.tight_layout()
save(fig, "fig_sem4_post_comparison")
# ═══════════════════════════════════════════════════════
# FIG SEM-5 : Summary statistics panel
# ═══════════════════════════════════════════════════════
fig, axes = plt.subplots(1, 2, figsize=(13, 5.5), facecolor=FIG_BG)
fig.suptitle("Figure SEM-5: Summary — Effect Sizes and Statistical Significance\n"
"SEM Tubule Occlusion Analysis",
fontsize=12, fontweight="bold", color="#1D3557", y=1.01)
# ── Left: Effect size bar chart
ax = axes[0]
labels_es = ["Within-Group\nGroup 1 (r)", "Within-Group\nGroup 2 (r)",
"Between-Group\nPOST (r)", "Between-Group\nChange (|r|)"]
es_vals = [0.886, 0.886, 0.84, 0.78]
es_cols = [C1, C2, "#2A9D8F", "#E9C46A"]
es_p = ["p=0.002", "p=0.002", "p=0.002", "p=0.004"]
y_es = np.arange(len(labels_es))
hb = ax.barh(y_es, es_vals, 0.45, color=es_cols, edgecolor="white",
linewidth=0.8, zorder=3)
for bar, val, p in zip(hb, es_vals, es_p):
ax.text(val+0.01, bar.get_y()+bar.get_height()/2,
f"r = {val} ({p})",
va="center", fontsize=9, fontweight="bold", color="#333")
ax.axvline(0.5, color="#AAA", linestyle=":", linewidth=1.0)
ax.axvline(0.8, color="#666", linestyle="--", linewidth=1.0)
ax.text(0.51, -0.6, "Medium\n(r=0.5)", fontsize=7.5, color="#AAA")
ax.text(0.81, -0.6, "Large\n(r=0.8)", fontsize=7.5, color="#666")
ax.set_yticks(y_es); ax.set_yticklabels(labels_es, fontsize=9)
ax.set_xlim(0, 1.05)
ax.set_xlabel("Effect Size (r)", fontsize=9.5, color="#444")
style(ax, "(A) Effect Size — All Comparisons\n[All qualify as LARGE effect, r > 0.8]")
# ── Right: p-value summary table rendered as table
ax2 = axes[1]
ax2.axis("off")
ax2.set_facecolor(PAN_BG)
ax2.set_title("(B) Statistical Summary Table", fontsize=10.5,
fontweight="bold", color="#1D3557", pad=7)
col_labels = ["Comparison", "Test", "Statistic", "p-value", "Effect r", "Significance"]
rows = [
["Within — Group 1\n(PRE vs POST)", "Wilcoxon", "W = 0.0", "0.0020", "0.886", "** Large"],
["Within — Group 2\n(PRE vs POST)", "Wilcoxon", "W = 0.0", "0.0020", "0.886", "** Large"],
["Between — POST\n(G1 vs G2)", "Mann–Whitney", "U = 8.0", "0.0015", "0.840", "** Large"],
["Between — Change\n(G1 vs G2)", "Mann–Whitney", "U = 89.0","0.0036", "0.780", "** Large"],
]
col_w = [0.26, 0.16, 0.14, 0.12, 0.12, 0.18]
row_h = 0.16
x_start = 0.01
y_start = 0.88
# header
x = x_start
for label, w in zip(col_labels, col_w):
ax2.add_patch(FancyBboxPatch((x, y_start), w-0.01, row_h,
boxstyle="round,pad=0.01",
facecolor="#1D3557", edgecolor="white",
linewidth=0.5, transform=ax2.transAxes))
ax2.text(x + (w-0.01)/2, y_start + row_h/2, label,
ha="center", va="center", fontsize=7.8,
color="white", fontweight="bold",
transform=ax2.transAxes)
x += w
row_colors = ["#FEE8EA", "#EBF3FA", "#FEE8EA", "#EBF3FA"]
for ri, (row, rc) in enumerate(zip(rows, row_colors)):
y = y_start - (ri+1)*(row_h + 0.02)
x = x_start
for ci, (cell, w) in enumerate(zip(row, col_w)):
fc = "#FFF0D0" if ci == 5 else rc
ax2.add_patch(FancyBboxPatch((x, y), w-0.01, row_h,
boxstyle="round,pad=0.01",
facecolor=fc, edgecolor="white",
linewidth=0.5, transform=ax2.transAxes))
ax2.text(x + (w-0.01)/2, y + row_h/2, cell,
ha="center", va="center", fontsize=7.5,
color="#1D3557", transform=ax2.transAxes)
x += w
ax2.text(0.01, 0.06,
"All effect sizes qualify as LARGE (r ≥ 0.78).\n"
"** p < 0.01. Lower POST score = better tubule occlusion.",
transform=ax2.transAxes, fontsize=8, color="#555",
fontstyle="italic")
plt.tight_layout()
save(fig, "fig_sem5_summary")
print("\n✓ All 5 SEM figures saved (PNG + SVG).")
print("Files: fig_sem1_pre_post_tooth | fig_sem2_boxplots | "
"fig_sem3_change_scores | fig_sem4_post_comparison | fig_sem5_summary")
| Comparison | Test | Statistic | p-value | Effect r | Interpretation |
|---|---|---|---|---|---|
| Group 1: PRE vs POST | Wilcoxon | W = 0.0 | 0.0020** | 0.886 (Large) | Significant occlusion ↑ |
| Group 2: PRE vs POST | Wilcoxon | W = 0.0 | 0.0020** | 0.886 (Large) | Significant occlusion ↑ |
| POST scores: G1 vs G2 | Mann–Whitney | U = 8.0 | 0.0015** | 0.84 (Large) | G1 superior final occlusion |
| Change scores: G1 vs G2 | Mann–Whitney | U = 89.0 | 0.0036** | 0.78 (Large) | G1 greater improvement |
| Figure | Type | Content |
|---|---|---|
| Fig SEM-1 | Line plot (2-panel) | PRE vs POST scores per tooth — Group 1 & Group 2 separately |
| Fig SEM-2 | Box plots + strip | PRE vs POST within each group — paired lines connecting specimens |
| Fig SEM-3 | Grouped bar | Change scores (PRE−POST) per tooth — Group 1 vs Group 2 with median lines |
| Fig SEM-4 | Box + strip | POST scores only — Group 1 vs Group 2 (between-group) |
| Fig SEM-5 | Effect size bar + table | Summary panel — all r values and p-values |
pip install matplotlib numpy scipy
python sem_graphs.py
Note on SEM score direction: Throughout the write-up, lower POST score = better tubule occlusion — this is clearly annotated on all graphs. Confirm with your guide whether the scale in your study runs 0→5 (none→complete) or 5→0 (none→complete) and adjust the axis labels in the script accordingly if needed.