256 lines
9.8 KiB
Python
256 lines
9.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Linear Regression Analysis for Wire Resistance Data
|
||
Generates scatter plots with regression lines for 3 AWG gauges.
|
||
"""
|
||
|
||
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
import matplotlib
|
||
import os
|
||
|
||
# Set sleek matplotlib style
|
||
matplotlib.style.use('default')
|
||
plt.style.use('seaborn-v0_8-whitegrid')
|
||
plt.rcParams['font.size'] = 10
|
||
plt.rcParams['axes.titlesize'] = 12
|
||
plt.rcParams['axes.labelsize'] = 11
|
||
plt.rcParams['xtick.labelsize'] = 10
|
||
plt.rcParams['ytick.labelsize'] = 10
|
||
plt.rcParams['legend.fontsize'] = 10
|
||
plt.rcParams['figure.dpi'] = 300
|
||
plt.rcParams['savefig.dpi'] = 300
|
||
plt.rcParams['savefig.bbox'] = 'tight'
|
||
plt.rcParams['savefig.pad_inches'] = 0.2
|
||
|
||
# Data from raw-data.csv
|
||
# X: Probe distance (cm)
|
||
x = np.array([20, 40, 60, 80, 100])
|
||
|
||
# Y: Resistance (Ω) for each AWG gauge
|
||
y_26 = np.array([2.6, 5.6, 4.5, 9.3, 10.0])
|
||
y_29 = np.array([4.7, 8.9, 13.6, 18.0, 22.6])
|
||
y_32 = np.array([9.0, 18.1, 27.3, 36.3, 45.4])
|
||
|
||
# Outlier flag for 26 AWG: index 2 (L=60cm) is experimental outlier with value 4.5 Ω
|
||
# Expected value based on trend from other points: ~7.0-7.5 Ω
|
||
OUTLIER_26AWG_L60 = {
|
||
'index': 2,
|
||
'distance_cm': 60,
|
||
'measured_ohms': 4.5,
|
||
'expected_ohms': 6.4, # Using slope 0.0925 Ω/cm and intercept 0.85 Ω
|
||
'expected_range': '(7.0 - 7.5 Ω based on linear trend from other points)'
|
||
}
|
||
|
||
# Wire properties
|
||
awg_data = [
|
||
(26, 0.1285, y_26, 'tab:blue', '26 AWG'),
|
||
(29, 0.06424, y_29, 'tab:orange', '29 AWG'),
|
||
(32, 0.03204, y_32, 'tab:green', '32 AWG')
|
||
]
|
||
|
||
# Linear regression function
|
||
def linear_regression(x, y):
|
||
"""Perform linear regression and return slope, intercept, R²"""
|
||
slope, intercept = np.polyfit(x, y, 1)
|
||
y_pred = slope * x + intercept
|
||
ss_res = np.sum((y - y_pred) ** 2)
|
||
ss_tot = np.sum((y - np.mean(y)) ** 2)
|
||
r_squared = 1 - (ss_res / ss_tot)
|
||
return slope, intercept, r_squared
|
||
|
||
# Print results
|
||
print("=" * 60)
|
||
print("LINEAR REGRESSION RESULTS")
|
||
print("Resistance (Ω) vs Probe Distance (cm)")
|
||
print("=" * 60)
|
||
|
||
results = []
|
||
outlier_results = {}
|
||
|
||
for awg, area, y, color, label in awg_data:
|
||
slope, intercept, r_squared = linear_regression(x, y)
|
||
# Calculate resistivity: ρ = slope × area × 0.01 (since slope = R/L, ρ = R·A/L)
|
||
# Convert mm² to cm² (1 mm² = 0.01 cm²)
|
||
resistivity = slope * area * 0.01 # in Ω·cm
|
||
|
||
result = {
|
||
'awg': awg,
|
||
'area': area,
|
||
'slope': slope,
|
||
'resistivity': resistivity,
|
||
'intercept': intercept,
|
||
'r_squared': r_squared
|
||
}
|
||
|
||
# Check for 26 AWG outlier
|
||
if awg == 26:
|
||
# Identify outlier at L=60cm (index 2)
|
||
outlier_measured = y[OUTLIER_26AWG_L60['index']]
|
||
outlier_expected = OUTLIER_26AWG_L60['expected_ohms']
|
||
outlier_deviation = abs(outlier_measured - outlier_expected)
|
||
|
||
result['has_outlier'] = True
|
||
result['outlier_info'] = {
|
||
'distance_cm': OUTLIER_26AWG_L60['distance_cm'],
|
||
'measured_ohms': outlier_measured,
|
||
'expected_ohms': outlier_expected,
|
||
'expected_range': OUTLIER_26AWG_L60['expected_range'],
|
||
'deviation_ohms': outlier_deviation,
|
||
'index': OUTLIER_26AWG_L60['index']
|
||
}
|
||
|
||
# Calculate corrected values excluding the outlier
|
||
x_no_outlier = np.delete(x, OUTLIER_26AWG_L60['index'])
|
||
y_no_outlier = np.delete(y, OUTLIER_26AWG_L60['index'])
|
||
slope_corrected, intercept_corrected, r_squared_corrected = linear_regression(x_no_outlier, y_no_outlier)
|
||
resistivity_corrected = slope_corrected * area * 0.01
|
||
|
||
outlier_results[awg] = {
|
||
'slope_corrected': slope_corrected,
|
||
'intercept_corrected': intercept_corrected,
|
||
'r_squared_corrected': r_squared_corrected,
|
||
'resistivity_corrected': resistivity_corrected,
|
||
'outlier_measured': outlier_measured,
|
||
'outlier_expected': outlier_expected,
|
||
'outlier_deviation': outlier_deviation
|
||
}
|
||
|
||
result['corrected'] = {
|
||
'slope': slope_corrected,
|
||
'intercept': intercept_corrected,
|
||
'r_squared': r_squared_corrected,
|
||
'resistivity': resistivity_corrected
|
||
}
|
||
else:
|
||
result['has_outlier'] = False
|
||
|
||
results.append(result)
|
||
|
||
print(f"\n{label} (Area = {area} mm²):")
|
||
print(f" Equation: R = ({slope:+.4f} Ω/cm) × L + ({intercept:+.4f} Ω)")
|
||
print(f" Resistivity ρ = slope × area × 0.01 (mm² to cm²) = {resistivity:.4f} Ω·cm")
|
||
print(f" R² = {r_squared:.4f}")
|
||
print(f" Standard error in R: {np.sqrt(np.sum((y - (slope*x + intercept))**2) / (len(x)-2)):.4f} Ω")
|
||
|
||
# Print outlier information and corrected calculation for 26 AWG
|
||
if result['has_outlier']:
|
||
oi = result['outlier_info']
|
||
cc = result['corrected']
|
||
print(f"\n ⚠️ OUTLIER DETECTED at L={oi['distance_cm']} cm:")
|
||
print(f" Measured: {oi['measured_ohms']} Ω (includes outlier)")
|
||
print(f" Expected: {oi['expected_ohms']} Ω ({oi['expected_range']})")
|
||
print(f" Deviation: {oi['deviation_ohms']:.2f} Ω")
|
||
print(f"\n CORRECTED CALCULATION (excluding outlier at L={oi['distance_cm']} cm):")
|
||
print(f" Equation: R = ({cc['slope']:+.4f} Ω/cm) × L + ({cc['intercept']:+.4f} Ω)")
|
||
print(f" Resistivity ρ = {cc['resistivity']:.4f} Ω·cm")
|
||
print(f" R² = {cc['r_squared']:.4f}")
|
||
print(f" Standard error in R: {np.sqrt(np.sum((y_no_outlier - (cc['slope']*x_no_outlier + cc['intercept']))**2) / (len(x_no_outlier)-2)):.4f} Ω")
|
||
print(f"\n SUMMARY:")
|
||
print(f" With outlier: slope={slope:.4f} Ω/cm, R²={r_squared:.4f}")
|
||
print(f" Without outlier: slope={cc['slope']:.4f} Ω/cm, R²={cc['r_squared']:.4f}")
|
||
|
||
print("\n" + "=" * 60)
|
||
|
||
# Create single figure with all 3 wires on same plot
|
||
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
|
||
|
||
# Store regression results for legend
|
||
all_slopes = []
|
||
all_intercepts = []
|
||
all_r_squared = []
|
||
|
||
for awg, area, y, color, label in awg_data:
|
||
slope, intercept, r_squared = linear_regression(x, y)
|
||
all_slopes.append(slope)
|
||
all_intercepts.append(intercept)
|
||
all_r_squared.append(r_squared)
|
||
|
||
# Scatter plot for this wire
|
||
ax.scatter(x, y, color=color, s=100, edgecolors='black', linewidth=1, zorder=3, label=f'{label}')
|
||
|
||
# Regression line
|
||
x_line = np.linspace(10, 110, 100)
|
||
y_line = slope * x_line + intercept
|
||
ax.plot(x_line, y_line, color=color, linestyle='-', linewidth=2.5, alpha=0.8, zorder=2)
|
||
|
||
# Format axes
|
||
ax.set_xlabel('Probe Distance (cm)', fontsize=12, fontweight='bold')
|
||
ax.set_ylabel('Resistance (Ω)', fontsize=12, fontweight='bold')
|
||
ax.set_title('Resistance vs Probe Distance for Different Wire Gauges', fontsize=14, fontweight='bold', pad=15)
|
||
|
||
# Add legend
|
||
ax.legend(loc='upper left', fontsize=10, framealpha=0.9)
|
||
|
||
# Add equations legend
|
||
eq_text = '26 AWG: R = {:.4f}L + {:.4f}\n'.format(all_slopes[0], all_intercepts[0])
|
||
eq_text += '29 AWG: R = {:.4f}L + {:.4f}\n'.format(all_slopes[1], all_intercepts[1])
|
||
eq_text += '32 AWG: R = {:.4f}L + {:.4f}'.format(all_slopes[2], all_intercepts[2])
|
||
ax.text(0.98, 0.98, eq_text, transform=ax.transAxes, fontsize=9,
|
||
verticalalignment='top', horizontalalignment='right')
|
||
|
||
# Grid
|
||
ax.grid(True, linestyle='--', alpha=0.7)
|
||
ax.set_axisbelow(True)
|
||
|
||
# Limit axes
|
||
ax.set_xlim(15, 105)
|
||
y_max_data = max(np.max(y_26), np.max(y_29), np.max(y_32))
|
||
y_max_lines = max(all_slopes[0]*110+all_intercepts[0],
|
||
all_slopes[1]*110+all_intercepts[1],
|
||
all_slopes[2]*110+all_intercepts[2])
|
||
ax.set_ylim(0, max(y_max_data, y_max_lines) * 1.1)
|
||
|
||
# Save figure
|
||
output_file = 'wire_resistance_regression.png'
|
||
plt.savefig(output_file, format='png', bbox_inches='tight')
|
||
print(f"\nFigure saved as: {output_file}")
|
||
|
||
# Also save as PDF for higher quality
|
||
output_pdf = 'wire_resistance_regression.pdf'
|
||
plt.savefig(output_pdf, format='pdf', bbox_inches='tight')
|
||
print(f"Figure saved as: {output_pdf}")
|
||
|
||
plt.close()
|
||
|
||
# Print LaTeX inclusion code
|
||
print("\n" + "=" * 60)
|
||
print("LATEX INCLUSION CODE (add to your .tex file)")
|
||
print("=" * 60)
|
||
print("""
|
||
\\begin{figure*}
|
||
\\centering
|
||
\\includegraphics[width=\\textwidth]{wire_resistance_regression}
|
||
\\caption{Linear regression analysis of resistance vs probe distance for three wire gauges. Each subplot shows experimental data points (circles) and the best-fit linear regression line. The regression equation and R² value are displayed in each plot. Note: 26 AWG data contains an experimental outlier at L=60cm (4.5 Ω vs expected ~6.4 Ω).}
|
||
\\label{fig:wire_regression}
|
||
\\end{figure*}
|
||
""")
|
||
|
||
# Print summary table for LaTeX
|
||
print("\n" + "=" * 60)
|
||
print("LATEX TABLE CODE (for regression parameters)")
|
||
print("=" * 60)
|
||
print("""
|
||
\\begin{table}
|
||
\\caption{Linear regression parameters and calculated resistivity for each wire gauge. Note: 26 AWG data excluded outlier at L=60cm (measured 4.5 Ω vs expected 6.4 Ω) due to experimental uncertainty.}
|
||
\\label{tab:regression_params}
|
||
\\begin{tabular}{ccccc}
|
||
\\hline
|
||
AWG & Area (mm$^2$) & Slope (Ω/cm) & Resistivity (Ω·cm) & R² \\
|
||
\\hline
|
||
""")
|
||
for res in results:
|
||
if res['has_outlier'] and 'corrected' in res:
|
||
# Print 26 AWG with both values
|
||
print(f" {res['awg']} & {res['area']} & {res['slope']:.6f} (with outlier) & {res['resistivity']:.6f} & {res['r_squared']:.6f} \\\\")
|
||
print(f" & & {res['corrected']['slope']:.6f} (corrected) & {res['corrected']['resistivity']:.6f} & {res['corrected']['r_squared']:.6f} \\\\")
|
||
else:
|
||
print(f" {res['awg']} & {res['area']} & {res['slope']:.6f} & {res['resistivity']:.6f} & {res['r_squared']:.6f} \\\\")
|
||
print(""" \\hline
|
||
\\end{tabular}
|
||
\\begin{tablenotes}
|
||
\\item Note: 26 AWG corrected values exclude outlier at L=60cm (measured 4.5 Ω, expected 6.4 Ω).
|
||
\\end{tablenotes}
|
||
\\end{table}
|
||
""")
|