{ "cells": [ { "cell_type": "code", "execution_count": 2, "id": "5d58304b-e94b-428b-91c8-35d649d97dd7", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "usage: ipykernel_launcher.py [-h] --input INPUT [--outdir OUTDIR]\n", " [--response RESPONSE] [--area_col AREA_COL]\n", " [--fm_col FM_COL] [--recompute_sigma]\n", " [--sn_type {LB}]\n", "ipykernel_launcher.py: error: the following arguments are required: --input\n" ] }, { "ename": "SystemExit", "evalue": "2", "output_type": "error", "traceback": [ "An exception has occurred, use %tb to see the full traceback.\n", "\u001b[0;31mSystemExit\u001b[0m\u001b[0;31m:\u001b[0m 2\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/usr/lib/python3.13/site-packages/IPython/core/interactiveshell.py:3585: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.\n", " warn(\"To exit: use 'exit', 'quit', or Ctrl-D.\", stacklevel=1)\n" ] } ], "source": [ "\n", "#!/usr/bin/env python3\n", "# -*- coding: utf-8 -*-\n", "\"\"\"\n", "Taguchi analysis pipeline for FDM experiment (per user's thesis)\n", "- Reads a CSV with columns similar to:\n", " 'Eksperiment','Orijentacija','Visina sloja','Širina ekstruzije','Postotak ispune',\n", " 'Broj slojeva stijenke','A_ekv [mm^2]','Fm kN]','Sigma [Mpa]','SNR [dB]'\n", "- Cleans units to numeric, recomputes Sigma (optional) and SNR (LB, n=1),\n", "- Builds response tables (means, Δ), ranks factors, selects optimal levels by SNR,\n", "- Predicts response at optimal combination (additive model),\n", "- Runs Taguchi-style ANOVA on Sigma,\n", "- Saves CSV outputs + main-effects plots + LaTeX snippet.\n", "Usage:\n", " python taguchi_from_csv.py --input ispitni_rezultati.csv --outdir out_tlak\n", "\"\"\"\n", "import argparse, os, re, json\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "def norm_num(x):\n", " if pd.isna(x):\n", " return np.nan\n", " if isinstance(x, (int, float, np.number)):\n", " return float(x)\n", " s = str(x).strip()\n", " s = s.replace(',', '.')\n", " s = s.replace('%','')\n", " s = s.replace(' mm','')\n", " s = s.replace('MPa','').replace('Mpa','')\n", " s = s.replace('kN','').replace('kN]','').replace('[','').replace(']','')\n", " try:\n", " return float(s)\n", " except:\n", " return np.nan\n", "\n", "def compute_snr_lb(y):\n", " # larger-the-better; handles n=1 case\n", " y = pd.to_numeric(y, errors='coerce')\n", " return 20.0*np.log10(y.clip(lower=1e-12))\n", "\n", "def response_table(df, factor, col):\n", " t = df.groupby(factor, as_index=False)[col].mean()\n", " t[\"Delta (max-min)\"] = t[col].max() - t[col].min()\n", " t[\"Faktor\"] = factor\n", " return t\n", "\n", "def taguchi_anova(df, response, factors):\n", " y = df[response].astype(float)\n", " mu = y.mean()\n", " total_ss = ((y - mu)**2).sum()\n", " rows = []\n", " dof_used = 0\n", " ss_used = 0.0\n", " for f in factors:\n", " grp = df.groupby(f)[response].agg(['mean','count'])\n", " ss_f = (grp['count']*(grp['mean']-mu)**2).sum()\n", " dof_f = grp.shape[0]-1\n", " rows.append([f, ss_f, dof_f])\n", " dof_used += dof_f\n", " ss_used += ss_f\n", " err_ss = max(total_ss - ss_used, 0.0)\n", " err_dof = max(len(df)-1 - dof_used, 0)\n", " an = pd.DataFrame(rows, columns=[\"Factor\",\"SS\",\"DOF\"])\n", " an[\"MS\"] = an[\"SS\"]/an[\"DOF\"]\n", " an[\"Pct_contrib_%\"] = (an[\"SS\"]/total_ss*100.0) if total_ss>0 else np.nan\n", " err_row = pd.DataFrame([[\"Error\", err_ss, err_dof, (err_ss/err_dof) if err_dof>0 else np.nan, (err_ss/total_ss*100.0) if total_ss>0 else np.nan]],\n", " columns=[\"Factor\",\"SS\",\"DOF\",\"MS\",\"Pct_contrib_%\"])\n", " an = pd.concat([an, err_row], ignore_index=True)\n", " return an, mu, total_ss\n", "\n", "def main():\n", " ap = argparse.ArgumentParser()\n", " ap.add_argument(\"--input\", required=True, help=\"Path to CSV with results\")\n", " ap.add_argument(\"--outdir\", default=None, help=\"Output directory\")\n", " ap.add_argument(\"--response\", default=\"Sigma [Mpa]\", help=\"Response column to analyze (default Sigma [Mpa])\")\n", " ap.add_argument(\"--area_col\", default=\"A_ekv [mm^2]\", help=\"Area column if Sigma should be recomputed from Fm/Area\")\n", " ap.add_argument(\"--fm_col\", default=\"Fm kN]\", help=\"Force column (kN)\")\n", " ap.add_argument(\"--recompute_sigma\", action=\"store_true\", help=\"If set, recompute Sigma = Fm*1000/Area\")\n", " ap.add_argument(\"--sn_type\", default=\"LB\", choices=[\"LB\"], help=\"S/N type (only LB supported here)\")\n", " args = ap.parse_args()\n", "\n", " in_path = args.input\n", " outdir = args.outdir or (os.path.splitext(os.path.basename(in_path))[0] + \"_taguchi_out\")\n", " os.makedirs(outdir, exist_ok=True)\n", "\n", " df = pd.read_csv(in_path)\n", "\n", " # Standard column mapping / cleanup for known names\n", " rename_map = {\n", " \"Visina sloja\":\"Visina sloja [mm]\",\n", " \"Širina ekstruzije\":\"Širina ekstruzije [mm]\",\n", " \"Postotak ispune\":\"Postotak ispune [%]\",\n", " \"Broj slojeva stijenke\":\"Broj stijenki\",\n", " \"Sigma [MPa]\":\"Sigma [Mpa]\",\n", " \"Fm [kN]\":\"Fm kN]\",\n", " }\n", " df = df.rename(columns={k:v for k,v in rename_map.items() if k in df.columns})\n", "\n", " # Ensure numeric for relevant columns\n", " if \"Visina sloja [mm]\" in df.columns:\n", " df[\"Visina sloja [mm]\"] = df[\"Visina sloja [mm]\"].apply(norm_num)\n", " if \"Širina ekstruzije [mm]\" in df.columns:\n", " df[\"Širina ekstruzije [mm]\"] = df[\"Širina ekstruzije [mm]\"].apply(norm_num)\n", " if \"Postotak ispune [%]\" in df.columns:\n", " df[\"Postotak ispune [%]\"] = df[\"Postotak ispune [%]\"].apply(norm_num)\n", " if \"Broj stijenki\" in df.columns:\n", " df[\"Broj stijenki\"] = df[\"Broj stijenki\"].apply(norm_num)\n", " if args.area_col in df.columns:\n", " df[args.area_col] = df[args.area_col].apply(norm_num)\n", " if args.fm_col in df.columns:\n", " df[args.fm_col] = df[args.fm_col].apply(norm_num)\n", " if args.response in df.columns:\n", " df[args.response] = df[args.response].apply(norm_num)\n", "\n", " # Compute Sigma if asked or missing\n", " if args.recompute_sigma or args.response not in df.columns or df[args.response].isna().all():\n", " if args.fm_col in df.columns and args.area_col in df.columns:\n", " df[args.response] = (df[args.fm_col] * 1000.0) / df[args.area_col]\n", " else:\n", " raise SystemExit(\"Cannot recompute Sigma: missing Fm or Area columns\")\n", "\n", " # Compute SNR (LB)\n", " df[\"SNR_LB [dB]\"] = compute_snr_lb(df[args.response])\n", "\n", " # Save cleaned raw\n", " raw_out = os.path.join(outdir, \"0_raw_with_SNR.csv\")\n", " df.to_csv(raw_out, index=False)\n", "\n", " # Factors to analyze (auto detect from known list)\n", " candidate_factors = [\"Orijentacija\",\"Visina sloja [mm]\",\"Širina ekstruzije [mm]\",\"Postotak ispune [%]\",\"Broj stijenki\"]\n", " factors = [f for f in candidate_factors if f in df.columns]\n", " if len(factors) == 0:\n", " raise SystemExit(\"No known factor columns found. Expected some of: \" + \", \".join(candidate_factors))\n", "\n", " # Response tables and deltas\n", " resp_mu = pd.concat([response_table(df, f, args.response) for f in factors], ignore_index=True)\n", " resp_sn = pd.concat([response_table(df, f, \"SNR_LB [dB]\") for f in factors], ignore_index=True)\n", " resp_mu.to_csv(os.path.join(outdir, \"1_response_means_Sigma.csv\"), index=False)\n", " resp_sn.to_csv(os.path.join(outdir, \"2_response_means_SNR.csv\"), index=False)\n", "\n", " # Ranking (by Delta)\n", " rank_mu = resp_mu.groupby(\"Faktor\")[\"Delta (max-min)\"].max().sort_values(ascending=False).reset_index().rename(columns={\"Delta (max-min)\":\"Rang delta (Sigma)\"})\n", " rank_sn = resp_sn.groupby(\"Faktor\")[\"Delta (max-min)\"].max().sort_values(ascending=False).reset_index().rename(columns={\"Delta (max-min)\":\"Rang delta (SNR)\"})\n", " ranking = pd.merge(rank_mu, rank_sn, on=\"Faktor\")\n", " ranking.to_csv(os.path.join(outdir, \"3_factor_ranking.csv\"), index=False)\n", "\n", " # Optimal levels by SNR\n", " opt_levels = {f: df.groupby(f)[\"SNR_LB [dB]\"].mean().sort_values(ascending=False).index[0] for f in factors}\n", " opt_table = pd.DataFrame({\"Faktor\": list(opt_levels.keys()), \"Optimalna razina (po S/N)\": list(opt_levels.values())})\n", " opt_table.to_csv(os.path.join(outdir, \"4_optimal_levels.csv\"), index=False)\n", "\n", " # Prediction at optimal combo (additive model) on response\n", " grand_mean = df[args.response].mean()\n", " k = len(factors)\n", " pred_sigma = sum(df.groupby(f)[args.response].mean().loc[opt_levels[f]] for f in factors) - (k-1)*grand_mean\n", " grand_mean_snr = df[\"SNR_LB [dB]\"].mean()\n", " pred_snr = sum(df.groupby(f)[\"SNR_LB [dB]\"].mean().loc[opt_levels[f]] for f in factors) - (k-1)*grand_mean_snr\n", " pred_df = pd.DataFrame({\n", " \"Predikcija\": [\"Sigma_opt [MPa]\",\"SNR_opt [dB]\",\"Grand mean Sigma [MPa]\",\"Grand mean SNR [dB]\"],\n", " \"Vrijednost\": [pred_sigma, pred_snr, grand_mean, grand_mean_snr]\n", " })\n", " pred_df.to_csv(os.path.join(outdir, \"5_prediction.csv\"), index=False)\n", "\n", " # ANOVA (Taguchi-style) on response\n", " anova_df, mu_sigma, totss = taguchi_anova(df, args.response, factors)\n", " anova_df.to_csv(os.path.join(outdir, \"6_anova_sigma.csv\"), index=False)\n", "\n", " # Plots: main effects for SNR\n", " for f in factors:\n", " means = df.groupby(f)[\"SNR_LB [dB]\"].mean().reset_index()\n", " # numeric sort if possible\n", " try:\n", " means[f] = pd.to_numeric(means[f], errors=\"ignore\")\n", " means = means.sort_values(by=f)\n", " except:\n", " pass\n", " plt.figure()\n", " plt.plot(means[f], means[\"SNR_LB [dB]\"], marker=\"o\")\n", " plt.xlabel(f)\n", " plt.ylabel(\"S/N (LB) [dB]\")\n", " plt.title(f\"Main effect (S/N): {f}\")\n", " plt.tight_layout()\n", " plt.savefig(os.path.join(outdir, f\"main_effect_SNR_{f}.png\"), dpi=150)\n", " plt.close()\n", "\n", " # LaTeX snippet\n", " latex_lines = []\n", " latex_lines.append(r\"% --- Taguchi rezultati (S = Sigma [MPa], S/N larger-the-better) ---\")\n", " latex_lines.append(r\"\\subsection{Rezultati Taguchijeve metode}\")\n", " latex_lines.append(r\"U skladu s ortogonalnom matricom provedena je analiza s kriterijem \\textbf{što-veće-to-bolje}. Za svaku kombinaciju izračunat je S/N omjer \\((\\mathrm{S/N}=20\\log_{10}(\\sigma))\\) te su određeni glavni učinci po razinama i optimalna kombinacija.\")\n", "\n", " # Optimal levels\n", " latex_lines.append(r\"\\paragraph{Optimalne razine (po S/N).}\")\n", " latex_lines.append(opt_table.to_latex(index=False, escape=False))\n", " # Prediction\n", " latex_lines.append(r\"\\paragraph{Predikcija odziva na optimalnoj kombinaciji.}\")\n", " latex_lines.append(pred_df.to_latex(index=False, escape=False, float_format='%.2f'))\n", " # Ranking\n", " latex_lines.append(r\"\\paragraph{Rang utjecaja faktora.}\")\n", " latex_lines.append(ranking.to_latex(index=False, escape=False, float_format='%.3f'))\n", " # ANOVA\n", " an_fmt = anova_df.copy()\n", " for c in [\"SS\",\"MS\",\"Pct_contrib_%\"]:\n", " if c in an_fmt.columns:\n", " an_fmt[c] = an_fmt[c].astype(float).round(3)\n", " latex_lines.append(r\"\\paragraph{ANOVA (Taguchi).}\")\n", " latex_lines.append(an_fmt.to_latex(index=False, escape=False))\n", " latex_lines.append(r\"Napomena: budući da je \\(n{=}1\\), pogreška (Error) procijenjena je iz preostalih stupnjeva slobode (Taguchi pooling).\")\n", "\n", " with open(os.path.join(outdir, \"taguchi_results.tex\"), \"w\", encoding=\"utf-8\") as f:\n", " f.write(\"\\n\\n\".join(latex_lines))\n", "\n", " # Small JSON summary\n", " summary = {\n", " \"outdir\": outdir,\n", " \"factors\": factors,\n", " \"opt_levels\": opt_levels,\n", " \"pred_sigma\": pred_sigma,\n", " \"grand_mean_sigma\": grand_mean,\n", " }\n", " with open(os.path.join(outdir, \"summary.json\"), \"w\", encoding=\"utf-8\") as f:\n", " json.dump(summary, f, ensure_ascii=False, indent=2)\n", "\n", " print(\"Done. Outputs in:\", outdir)\n", "\n", "if __name__ == \"__main__\":\n", " main()" ] }, { "cell_type": "code", "execution_count": null, "id": "56399c17-5135-4fa3-8809-358fef72570b", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }