import%20marimo%0A%0A__generated_with%20%3D%20%220.16.4%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%23%20We%20Tested%205%20Outlier%20Detection%20Methods%20on%20Real%20Wine%20Data%3A%20They%20Disagreed%20on%2096%25%20of%20Flagged%20Samples%0A%0A%20%20%20%20*Out%20of%20816%20wines%20flagged%20by%20at%20least%20one%20method%2C%20just%2032%20made%20the%20unanimous%20list.%20Those%20wines%20had%20something%20in%20common.*%0A%0A%20%20%20%20---%0A%0A%20%20%20%20Every%20data%20science%20tutorial%20makes%20outlier%20detection%20look%20simple.%20Remove%20values%20beyond%203%20standard%20deviations%2C%20done.%20But%20when%20you're%20staring%20at%20a%20real%20dataset%20with%20skewed%20distributions%20and%20stakeholders%20asking%20%22why%20did%20you%20remove%20that%20data%20point%3F%22%20%E2%80%94%20things%20get%20complicated.%0A%0A%20%20%20%20So%20we%20ran%20an%20experiment.%20Five%20popular%20outlier%20detection%20methods%2C%20one%20real%20dataset%20(6%2C497%20Portuguese%20wines)%2C%20and%20a%20simple%20question%3A%20**how%20do%20these%20methods%20actually%20compare%3F**%0A%0A%20%20%20%20The%20results%20were%20illuminating%20%E2%80%94%20not%20because%20one%20method%20was%20%22right%2C%22%20but%20because%20they%20capture%20fundamentally%20different%20notions%20of%20%22unusual.%22%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%23%23%20The%20Setup%0A%0A%20%20%20%20We're%20using%20the%20**Wine%20Quality%20Dataset**%20from%20the%20UCI%20Machine%20Learning%20Repository.%20It%20contains%20physicochemical%20measurements%20from%206%2C497%20Portuguese%20%22Vinho%20Verde%22%20wines%20(both%20red%20and%20white)%2C%20along%20with%20quality%20ratings%20from%20expert%20tasters.%0A%0A%20%20%20%20Why%20this%20dataset%3F%0A%20%20%20%201.%20**Real%20data**%20from%20actual%20wine%20production%20(not%20synthetic)%0A%20%20%20%202.%20**Skewed%20distributions**%20%E2%80%94%20several%20features%20have%20long%20tails%2C%20which%20breaks%20assumptions%0A%20%20%20%203.%20**External%20reference**%20%E2%80%94%20quality%20ratings%20let%20us%20check%20if%20%22outliers%22%20correlate%20with%20unusual%20wines%0A%0A%20%20%20%20Our%20five%20contenders%3A%0A%0A%20%20%20%20%7C%20Method%20%7C%20Type%20%7C%20Assumption%20%7C%20Detects%20%7C%0A%20%20%20%20%7C--------%7C------%7C------------%7C----------%7C%0A%20%20%20%20%7C%20Z-Score%20%7C%20Statistical%20%7C%20Normal%20distribution%20%7C%20Univariate%20extremes%20%7C%0A%20%20%20%20%7C%20IQR%20%7C%20Statistical%20%7C%20None%20(non-parametric)%20%7C%20Univariate%20extremes%20%7C%0A%20%20%20%20%7C%20Isolation%20Forest%20%7C%20ML%20(tree-based)%20%7C%20Outliers%20isolate%20easily%20%7C%20Globally%20%22different%22%20points%20%7C%0A%20%20%20%20%7C%20Local%20Outlier%20Factor%20%7C%20ML%20(density)%20%7C%20Similar%20local%20density%20%7C%20Local%20density%20deviations%20%7C%0A%20%20%20%20%7C%20Elliptic%20Envelope%20%7C%20ML%20(Gaussian)%20%7C%20Multivariate%20normal%20%7C%20Mahalanobis%20distance%20extremes%20%7C%0A%0A%20%20%20%20**Important%20methodological%20notes%3A**%0A%20%20%20%20-%20ML%20methods%20use%20%60contamination%3D0.05%60%2C%20which%20*forces*%20them%20to%20flag%20exactly%205%25%0A%20%20%20%20-%20We%20combine%20red%20and%20white%20wines%20(mixture%20distribution)%20%E2%80%94%20we%20use%20**stratified%20scaling**%20to%20mitigate%20confounds%0A%20%20%20%20-%20Statistical%20methods%20use%20per-feature%20thresholds%20with%20%60min_features%3D2%60%20to%20reduce%20multiple-testing%20inflation%20(without%20this%2C%20both%20Z-Score%20and%20IQR%20flag%20~23-26%25%20of%20data%20as%20outliers)%0A%20%20%20%20-%20With%2011%20features%2C%20we're%20in%20a%20moderate-dimensional%20regime%3B%20LOF%20may%20behave%20differently%20at%20higher%20dimensions%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Standard%20imports%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20seaborn%20as%20sns%0A%20%20%20%20from%20scipy%20import%20stats%0A%20%20%20%20import%20warnings%0A%0A%20%20%20%20%23%20ML%20methods%0A%20%20%20%20from%20sklearn.ensemble%20import%20IsolationForest%0A%20%20%20%20from%20sklearn.neighbors%20import%20LocalOutlierFactor%0A%20%20%20%20from%20sklearn.covariance%20import%20EllipticEnvelope%0A%20%20%20%20from%20sklearn.preprocessing%20import%20RobustScaler%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20EllipticEnvelope%2C%0A%20%20%20%20%20%20%20%20IsolationForest%2C%0A%20%20%20%20%20%20%20%20LocalOutlierFactor%2C%0A%20%20%20%20%20%20%20%20RobustScaler%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20pd%2C%0A%20%20%20%20%20%20%20%20plt%2C%0A%20%20%20%20%20%20%20%20sns%2C%0A%20%20%20%20%20%20%20%20stats%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20plt)%3A%0A%20%20%20%20%23%20Visualization%20settings%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20plt.style.use('seaborn-v0_8-whitegrid')%0A%20%20%20%20except%20OSError%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20plt.style.use('seaborn-whitegrid')%0A%20%20%20%20%20%20%20%20except%20OSError%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pass%20%20%23%20Use%20default%20style%0A%0A%20%20%20%20plt.rcParams%5B'figure.figsize'%5D%20%3D%20(12%2C%206)%0A%20%20%20%20plt.rcParams%5B'font.size'%5D%20%3D%2011%0A%0A%20%20%20%20%23%20For%20reproducibility%0A%20%20%20%20np.random.seed(42)%0A%0A%20%20%20%20print(%22Setup%20complete.%20Let's%20find%20some%20outliers%20in%20wine%20data.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20Loading%20the%20Wine%20Quality%20Data%0A%0A%20%20%20%20The%20dataset%20contains%20red%20and%20white%20wines.%20We'll%20combine%20them%20but%20use%20**stratified%20scaling**%20to%20prevent%20mixture%20artifacts.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd)%3A%0A%20%20%20%20%23%20Load%20Wine%20Quality%20dataset%0A%20%20%20%20red_url%20%3D%20%22https%3A%2F%2Farchive.ics.uci.edu%2Fml%2Fmachine-learning-databases%2Fwine-quality%2Fwinequality-red.csv%22%0A%20%20%20%20white_url%20%3D%20%22https%3A%2F%2Farchive.ics.uci.edu%2Fml%2Fmachine-learning-databases%2Fwine-quality%2Fwinequality-white.csv%22%0A%0A%20%20%20%20red%20%3D%20pd.read_csv(red_url%2C%20sep%3D'%3B')%0A%20%20%20%20white%20%3D%20pd.read_csv(white_url%2C%20sep%3D'%3B')%0A%20%20%20%20return%20red%2C%20white%0A%0A%0A%40app.cell%0Adef%20_(pd%2C%20red%2C%20white)%3A%0A%20%20%20%20%23%20Add%20wine%20type%20indicator%0A%20%20%20%20red%5B'wine_type'%5D%20%3D%20'red'%0A%20%20%20%20white%5B'wine_type'%5D%20%3D%20'white'%0A%0A%20%20%20%20%23%20Combine%20datasets%0A%20%20%20%20df%20%3D%20pd.concat(%5Bred%2C%20white%5D%2C%20ignore_index%3DTrue)%0A%0A%20%20%20%20print(f%22Dataset%20loaded%20successfully!%22)%0A%20%20%20%20print(f%22%20%20Red%20wines%3A%20%20%20%7Blen(red)%3A%2C%7D%22)%0A%20%20%20%20print(f%22%20%20White%20wines%3A%20%7Blen(white)%3A%2C%7D%22)%0A%20%20%20%20print(f%22%20%20Total%3A%20%20%20%20%20%20%20%7Blen(df)%3A%2C%7D%22)%0A%20%20%20%20return%20(df%2C)%0A%0A%0A%40app.cell%0Adef%20_(df)%3A%0A%20%20%20%20%23%20Define%20feature%20columns%20(exclude%20quality%20rating%20and%20wine%20type)%0A%20%20%20%20feature_cols%20%3D%20%5Bc%20for%20c%20in%20df.columns%20if%20c%20not%20in%20%5B'quality'%2C%20'wine_type'%5D%5D%0A%0A%20%20%20%20print(f%22Analyzing%20%7Blen(feature_cols)%7D%20physicochemical%20features%3A%22)%0A%20%20%20%20for%20col%20in%20feature_cols%3A%0A%20%20%20%20%20%20%20%20print(f%22%20%20%E2%80%A2%20%7Bcol%7D%22)%0A%20%20%20%20return%20(feature_cols%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20Stratified%20Scaling%0A%0A%20%20%20%20Red%20and%20white%20wines%20have%20fundamentally%20different%20chemical%20profiles%20(e.g.%2C%20white%20wines%20typically%20have%20much%20higher%20total%20sulfur%20dioxide).%20If%20we%20scale%20them%20together%2C%20a%20%22perfectly%20average%22%20red%20wine%20might%20be%20flagged%20as%20an%20outlier%20because%20its%20sulfur%20levels%20are%20%22extreme%22%20compared%20to%20the%20combined%20mean.%0A%0A%20%20%20%20**Solution%3A**%20Scale%20each%20wine%20type%20independently%2C%20then%20recombine.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(RobustScaler%2C%20pd)%3A%0A%20%20%20%20def%20stratified_robust_scale(df%2C%20feature_cols%2C%20group_col%3D'wine_type')%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Apply%20RobustScaler%20separately%20to%20each%20group%2C%20then%20recombine.%0A%20%20%20%20%20%20%20%20This%20prevents%20mixture%20artifacts%20where%20one%20group's%20normal%20values%0A%20%20%20%20%20%20%20%20appear%20extreme%20relative%20to%20the%20combined%20distribution.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20scaled_dfs%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20scalers%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20for%20group_name%2C%20group_df%20in%20df.groupby(group_col)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20scaler%20%3D%20RobustScaler()%0A%20%20%20%20%20%20%20%20%20%20%20%20scaled_features%20%3D%20scaler.fit_transform(group_df%5Bfeature_cols%5D)%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Create%20scaled%20dataframe%20preserving%20index%0A%20%20%20%20%20%20%20%20%20%20%20%20scaled_df%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scaled_features%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20columns%3Dfeature_cols%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3Dgroup_df.index%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20scaled_dfs.append(scaled_df)%0A%20%20%20%20%20%20%20%20%20%20%20%20scalers%5Bgroup_name%5D%20%3D%20scaler%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%23%20Recombine%20in%20original%20order%0A%20%20%20%20%20%20%20%20combined%20%3D%20pd.concat(scaled_dfs).sort_index()%0A%20%20%20%20%20%20%20%20return%20combined%2C%20scalers%0A%20%20%20%20return%20(stratified_robust_scale%2C)%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20feature_cols%2C%20stratified_robust_scale)%3A%0A%20%20%20%20%23%20Create%20stratified-scaled%20version%20for%20ML%20methods%0A%20%20%20%20X_stratified_scaled%2C%20scalers_by_type%20%3D%20stratified_robust_scale(df%2C%20feature_cols)%0A%0A%20%20%20%20print(%22Stratified%20scaling%20complete.%22)%0A%20%20%20%20print(f%22%20%20Red%20wines%20scaled%20with%20median%2FIQR%20from%20red%20wines%20only%22)%0A%20%20%20%20print(f%22%20%20White%20wines%20scaled%20with%20median%2FIQR%20from%20white%20wines%20only%22)%0A%20%20%20%20return%20(X_stratified_scaled%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%20Exploratory%20Data%20Analysis%3A%20Why%20This%20Dataset%20Is%20Tricky%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20feature_cols)%3A%0A%20%20%20%20%23%20THE%20KEY%20ISSUE%3A%20Skewness%0A%20%20%20%20print(%22Feature%20Skewness%20Analysis%22)%0A%20%20%20%20print(%22%3D%22%20*%2060)%0A%20%20%20%20print(f%22%7B'Feature'%3A%3C25%7D%20%7B'Skewness'%3A%3E10%7D%20%7B'Status'%3A%3C20%7D%22)%0A%20%20%20%20print(%22-%22%20*%2060)%0A%20%20%20%20skewness_data%20%3D%20%5B%5D%0A%20%20%20%20for%20feature_name%20in%20feature_cols%3A%0A%20%20%20%20%20%20%20%20skewness_value%20%3D%20df%5Bfeature_name%5D.skew()%0A%20%20%20%20%20%20%20%20if%20abs(skewness_value)%20%3E%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20%22%E2%9A%A0%EF%B8%8F%20%20HIGHLY%20SKEWED%22%0A%20%20%20%20%20%20%20%20elif%20abs(skewness_value)%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20%22moderate%22%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20%22~normal%22%0A%20%20%20%20%20%20%20%20print(f%22%7Bfeature_name%3A%3C25%7D%20%7Bskewness_value%3A%3E10.2f%7D%20%7Bstatus%3A%3C20%7D%22)%0A%20%20%20%20%20%20%20%20skewness_data.append(%7B'feature'%3A%20feature_name%2C%20'skewness'%3A%20skewness_value%7D)%0A%20%20%20%20highly_skewed%20%3D%20sum(1%20for%20s%20in%20skewness_data%20if%20abs(s%5B'skewness'%5D)%20%3E%201)%0A%20%20%20%20print(f%22%5Cn%E2%86%92%20%7Bhighly_skewed%7D%20of%20%7Blen(feature_cols)%7D%20features%20are%20highly%20skewed!%22)%0A%20%20%20%20print(%22%20%20This%20violates%20Z-Score's%20normality%20assumption%20and%20affects%20Elliptic%20Envelope.%22)%0A%20%20%20%20return%20(highly_skewed%2C)%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20feature_cols%2C%20plt%2C%20sns)%3A%0A%20%20%20%20%23%20Letter-value%20plots%20(better%20for%20skewed%20data%20than%20standard%20boxplots)%0A%20%20%20%20fig_lettervalue%2C%20axes_lettervalue%20%3D%20plt.subplots(3%2C%204%2C%20figsize%3D(16%2C%2010))%0A%20%20%20%20axes_lettervalue%20%3D%20axes_lettervalue.flatten()%0A%20%20%20%20for%20idx%2C%20feature_col%20in%20enumerate(feature_cols)%3A%0A%20%20%20%20%20%20%20%20ax_lv%20%3D%20axes_lettervalue%5Bidx%5D%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20sns.boxenplot(y%3Ddf%5Bfeature_col%5D%2C%20ax%3Dax_lv%2C%20color%3D'coral')%0A%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax_lv.boxplot(df%5Bfeature_col%5D%2C%20patch_artist%3DTrue)%0A%20%20%20%20%20%20%20%20feature_skew%20%3D%20df%5Bfeature_col%5D.skew()%0A%20%20%20%20%20%20%20%20title_color%20%3D%20'red'%20if%20abs(feature_skew)%20%3E%201%20else%20'black'%0A%20%20%20%20%20%20%20%20ax_lv.set_title(f%22%7Bfeature_col%7D%5Cn(skew%3A%20%7Bfeature_skew%3A.2f%7D)%22%2C%20fontsize%3D10%2C%20color%3Dtitle_color)%0A%20%20%20%20%20%20%20%20ax_lv.set_xlabel('')%0A%20%20%20%20axes_lettervalue%5B-1%5D.set_visible(False)%0A%20%20%20%20plt.suptitle('Letter-Value%20Plots%20%E2%80%94%20Red%20titles%20indicate%20highly%20skewed%20features'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fontsize%3D14%2C%20fontweight%3D'bold'%2C%20y%3D1.02)%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Method%201%3A%20Robust%20Z-Score%0A%0A%20%20%20%20We%20use%20**robust%20Z-score%20(median%2FMAD)**%20as%20the%20primary%20approach%20%E2%80%94%20it%20handles%20skewed%20data%20better%20than%20standard%20Z-score%20(mean%2Fstd).%0A%0A%20%20%20%20**Multiple%20Testing%3A**%20With%2011%20features%2C%20%60min_features%3D1%60%20would%20flag%20a%20row%20if%20ANY%20feature%20is%20extreme.%20This%20dramatically%20inflates%20false%20positives%20%E2%80%94%20we'll%20see%20both%20Z-Score%20and%20IQR%20flag%2023-26%25%20of%20data%20with%20%60min_features%3D1%60%2C%20dropping%20to%203-7%25%20with%20%60min_features%3D2%60.%20We%20use%20%60min_features%3D2%60%20as%20primary.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pd)%3A%0A%20%20%20%20def%20detect_outliers_zscore_robust(df%2C%20columns%2C%20threshold%3D3.5%2C%20min_features%3D2)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Detect%20outliers%20using%20ROBUST%20(Modified)%20Z-score%20with%20median%20and%20MAD.%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20Modified%20Z-score%20%3D%200.6745%20*%20(x%20-%20median)%20%2F%20MAD%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20Parameters%3A%0A%20%20%20%20%20%20%20%20-----------%0A%20%20%20%20%20%20%20%20threshold%20%3A%20float%0A%20%20%20%20%20%20%20%20%20%20%20%203.5%20is%20commonly%20recommended%20for%20modified%20Z-score%0A%20%20%20%20%20%20%20%20min_features%20%3A%20int%0A%20%20%20%20%20%20%20%20%20%20%20%20Minimum%20features%20that%20must%20be%20outlying%20(default%202%20to%20reduce%20inflation)%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20outlier_mask%20%3D%20pd.DataFrame(index%3Ddf.index)%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20for%20col%20in%20columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20median%20%3D%20df%5Bcol%5D.median()%0A%20%20%20%20%20%20%20%20%20%20%20%20mad%20%3D%20np.median(np.abs(df%5Bcol%5D%20-%20median))%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20PROPER%20MAD%3D0%20handling%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20MAD%3D0%20means%20%3E50%25%20of%20values%20are%20identical%20(discrete%2Fconcentrated%20variable)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20mad%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Fall%20back%20to%20scaled%20IQR%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20iqr%20%3D%20df%5Bcol%5D.quantile(0.75)%20-%20df%5Bcol%5D.quantile(0.25)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mad%20%3D%20iqr%20*%200.7413%20%20%23%20Scale%20factor%20to%20match%20MAD%20for%20normal%20distribution%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20mad%20%3D%3D%200%3A%20%20%23%20Still%20zero%20%3D%20constant%20or%20near-constant%20feature%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20outlier_mask%5Bcol%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20modified_z%20%3D%200.6745%20*%20(df%5Bcol%5D%20-%20median)%20%2F%20mad%0A%20%20%20%20%20%20%20%20%20%20%20%20outlier_mask%5Bcol%5D%20%3D%20np.abs(modified_z)%20%3E%20threshold%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20outlier_counts%20%3D%20outlier_mask.sum(axis%3D1)%0A%20%20%20%20%20%20%20%20any_outlier%20%3D%20outlier_counts%20%3E%3D%20min_features%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20any_outlier%2C%20outlier_mask%0A%20%20%20%20return%20(detect_outliers_zscore_robust%2C)%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pd%2C%20stats)%3A%0A%20%20%20%20def%20detect_outliers_zscore_standard(df%2C%20columns%2C%20threshold%3D3%2C%20min_features%3D2)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Standard%20Z-score%20(mean%2Fstd)%20-%20shown%20for%20comparison%20with%20tutorials.%0A%20%20%20%20%20%20%20%20Less%20robust%20to%20existing%20outliers%20and%20skewed%20data.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Vectorized%20implementation%0A%20%20%20%20%20%20%20%20z_scores%20%3D%20np.abs(stats.zscore(df%5Bcolumns%5D%2C%20nan_policy%3D'omit'))%0A%20%20%20%20%20%20%20%20outlier_mask%20%3D%20pd.DataFrame(z_scores%20%3E%20threshold%2C%20index%3Ddf.index%2C%20columns%3Dcolumns)%0A%20%20%20%20%20%20%20%20any_outlier%20%3D%20outlier_mask.sum(axis%3D1)%20%3E%3D%20min_features%0A%20%20%20%20%20%20%20%20return%20any_outlier%2C%20outlier_mask%0A%20%20%20%20return%20(detect_outliers_zscore_standard%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20detect_outliers_zscore_robust%2C%0A%20%20%20%20detect_outliers_zscore_standard%2C%0A%20%20%20%20df%2C%0A%20%20%20%20feature_cols%2C%0A)%3A%0A%20%20%20%20%23%20PRIMARY%3A%20Robust%20Z-score%20with%20min_features%3D2%0A%20%20%20%20zscore_outliers%2C%20zscore_details%20%3D%20detect_outliers_zscore_robust(df%2C%20feature_cols%2C%20min_features%3D2)%0A%0A%20%20%20%20%23%20Secondary%3A%20variants%20for%20comparison%0A%20%20%20%20zscore_aggressive%2C%20_%20%3D%20detect_outliers_zscore_robust(df%2C%20feature_cols%2C%20min_features%3D1)%0A%20%20%20%20zscore_standard%2C%20zscore_standard_details%20%3D%20detect_outliers_zscore_standard(df%2C%20feature_cols%2C%20min_features%3D2)%0A%0A%20%20%20%20print(f%22Z-Score%20Results%3A%22)%0A%20%20%20%20print(f%22%20%20Robust%20(2%2B%20features)%20%5BPRIMARY%5D%3A%20%7Bzscore_outliers.sum()%3A%2C%7D%20(%7Bzscore_outliers.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%20%20Robust%20(any%20feature)%3A%20%20%20%20%20%20%20%20%20%20%20%7Bzscore_aggressive.sum()%3A%2C%7D%20(%7Bzscore_aggressive.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%20%20Standard%20(2%2B%20features)%3A%20%20%20%20%20%20%20%20%20%7Bzscore_standard.sum()%3A%2C%7D%20(%7Bzscore_standard.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%5Cn%E2%9A%A0%EF%B8%8F%20%20The%20'any%20feature'%20variant%20flags%20%7Bzscore_aggressive.mean()*100%3A.0f%7D%25%20%E2%80%94%20multiple%20testing%20inflation!%22)%0A%20%20%20%20print(f%22%E2%86%92%20Requiring%202%2B%20features%20reduces%20count%20from%20%7Bzscore_aggressive.mean()*100%3A.0f%7D%25%20to%20%7Bzscore_outliers.mean()*100%3A.1f%7D%25%20(7x%20reduction).%22)%0A%20%20%20%20return%20zscore_aggressive%2C%20zscore_outliers%2C%20zscore_standard%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Method%202%3A%20IQR%0A%0A%20%20%20%20Non-parametric%2C%20but%20the%201.5%C3%97%20multiplier%20can%20be%20aggressive%20on%20skewed%20data.%20We%20use%20%60min_features%3D2%60%20as%20primary.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.function%0Adef%20detect_outliers_iqr(df%2C%20columns%2C%20multiplier%3D1.5%2C%20min_features%3D2)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Detect%20outliers%20using%20IQR%20method%20(vectorized).%0A%20%20%20%20%22%22%22%0A%20%20%20%20X%20%3D%20df%5Bcolumns%5D%0A%20%20%20%20Q1%20%3D%20X.quantile(0.25)%0A%20%20%20%20Q3%20%3D%20X.quantile(0.75)%0A%20%20%20%20IQR%20%3D%20Q3%20-%20Q1%0A%20%20%20%20%0A%20%20%20%20lower%20%3D%20Q1%20-%20multiplier%20*%20IQR%0A%20%20%20%20upper%20%3D%20Q3%20%2B%20multiplier%20*%20IQR%0A%20%20%20%20%0A%20%20%20%20outlier_mask%20%3D%20(X%20%3C%20lower)%20%7C%20(X%20%3E%20upper)%0A%20%20%20%20any_outlier%20%3D%20outlier_mask.sum(axis%3D1)%20%3E%3D%20min_features%0A%20%20%20%20%0A%20%20%20%20return%20any_outlier%2C%20outlier_mask%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20feature_cols)%3A%0A%20%20%20%20%23%20PRIMARY%3A%20IQR%20with%20min_features%3D2%0A%20%20%20%20iqr_outliers%2C%20iqr_details%20%3D%20detect_outliers_iqr(df%2C%20feature_cols%2C%20multiplier%3D1.5%2C%20min_features%3D2)%0A%0A%20%20%20%20%23%20Secondary%20variants%0A%20%20%20%20iqr_aggressive%2C%20_%20%3D%20detect_outliers_iqr(df%2C%20feature_cols%2C%20multiplier%3D1.5%2C%20min_features%3D1)%0A%20%20%20%20iqr_conservative%2C%20_%20%3D%20detect_outliers_iqr(df%2C%20feature_cols%2C%20multiplier%3D3.0%2C%20min_features%3D1)%0A%0A%20%20%20%20print(f%22IQR%20Results%3A%22)%0A%20%20%20%20print(f%22%20%201.5x%20(2%2B%20features)%20%5BPRIMARY%5D%3A%20%7Biqr_outliers.sum()%3A%2C%7D%20(%7Biqr_outliers.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%20%201.5x%20(any%20feature)%3A%20%20%20%20%20%20%20%20%20%20%20%7Biqr_aggressive.sum()%3A%2C%7D%20(%7Biqr_aggressive.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%20%203.0x%20(any%20feature)%3A%20%20%20%20%20%20%20%20%20%20%20%7Biqr_conservative.sum()%3A%2C%7D%20(%7Biqr_conservative.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%5Cn%E2%9A%A0%EF%B8%8F%20%20The%20'any%20feature'%20variant%20flags%20%7Biqr_aggressive.mean()*100%3A.0f%7D%25%20%E2%80%94%20same%20inflation%20pattern%20as%20Z-Score%20above.%22)%0A%20%20%20%20print(f%22%E2%86%92%20Requiring%202%2B%20features%20reduces%20count%20from%20%7Biqr_aggressive.mean()*100%3A.0f%7D%25%20to%20%7Biqr_outliers.mean()*100%3A.1f%7D%25%20(3x%20reduction).%22)%0A%20%20%20%20return%20iqr_aggressive%2C%20iqr_outliers%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Method%203%3A%20Isolation%20Forest%0A%0A%20%20%20%20Tree-based%20method%20that%20doesn't%20assume%20normality.%20Uses%20**stratified-scaled**%20data.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(IsolationForest)%3A%0A%20%20%20%20def%20detect_outliers_isolation_forest(X_scaled%2C%20contamination%3D0.05)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Detect%20outliers%20using%20Isolation%20Forest.%0A%20%20%20%20%20%20%20%20Note%3A%20contamination%20FORCES%20exactly%20that%20proportion%20to%20be%20flagged.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20iso%20%3D%20IsolationForest(%0A%20%20%20%20%20%20%20%20%20%20%20%20contamination%3Dcontamination%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3D42%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3D100%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20predictions%20%3D%20iso.fit_predict(X_scaled)%0A%20%20%20%20%20%20%20%20scores%20%3D%20iso.decision_function(X_scaled)%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20predictions%20%3D%3D%20-1%2C%20scores%0A%20%20%20%20return%20(detect_outliers_isolation_forest%2C)%0A%0A%0A%40app.cell%0Adef%20_(X_stratified_scaled%2C%20detect_outliers_isolation_forest)%3A%0A%20%20%20%20%23%20Use%20stratified-scaled%20data%0A%20%20%20%20iso_outliers%2C%20iso_scores%20%3D%20detect_outliers_isolation_forest(X_stratified_scaled)%0A%0A%20%20%20%20print(f%22Isolation%20Forest%20Results%20(contamination%3D5%25%2C%20stratified%20scaling)%3A%22)%0A%20%20%20%20print(f%22%20%20Total%20outliers%3A%20%7Biso_outliers.sum()%3A%2C%7D%20(%7Biso_outliers.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20return%20iso_outliers%2C%20iso_scores%0A%0A%0A%40app.cell%0Adef%20_(iso_outliers%2C%20iso_scores%2C%20np%2C%20plt)%3A%0A%20%20%20%20%23%20Visualize%20with%20FIXED%20sorted-score%20plot%0A%20%20%20%20plt.figure(figsize%3D(12%2C%204))%0A%0A%20%20%20%20plt.subplot(1%2C%202%2C%201)%0A%20%20%20%20plt.hist(iso_scores%2C%20bins%3D50%2C%20color%3D'forestgreen'%2C%20edgecolor%3D'white'%2C%20alpha%3D0.7)%0A%20%20%20%20if%20iso_outliers.any()%3A%0A%20%20%20%20%20%20%20%20threshold%20%3D%20iso_scores%5Biso_outliers%5D.max()%0A%20%20%20%20%20%20%20%20plt.axvline(x%3Dthreshold%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D2%2C%20label%3D'Threshold')%0A%20%20%20%20plt.xlabel('Anomaly%20Score')%0A%20%20%20%20plt.ylabel('Count')%0A%20%20%20%20plt.title('Isolation%20Forest%3A%20Score%20Distribution')%0A%20%20%20%20plt.legend()%0A%0A%20%20%20%20plt.subplot(1%2C%202%2C%202)%0A%20%20%20%20%23%20Sort%20both%20scores%20AND%20outlier%20flags%20together%0A%20%20%20%20sort_order%20%3D%20np.argsort(iso_scores)%0A%20%20%20%20sorted_scores%20%3D%20iso_scores%5Bsort_order%5D%0A%20%20%20%20sorted_outliers%20%3D%20iso_outliers%5Bsort_order%5D%0A%20%20%20%20colors%20%3D%20%5B'red'%20if%20x%20else%20'forestgreen'%20for%20x%20in%20sorted_outliers%5D%0A%20%20%20%20plt.scatter(range(len(sorted_scores))%2C%20sorted_scores%2C%20c%3Dcolors%2C%20alpha%3D0.3%2C%20s%3D5)%0A%20%20%20%20plt.xlabel('Sample%20Index%20(sorted%20by%20score)')%0A%20%20%20%20plt.ylabel('Anomaly%20Score')%0A%20%20%20%20plt.title('Sorted%20Scores%20(red%20%3D%20outlier)')%0A%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Method%204%3A%20Local%20Outlier%20Factor%20(LOF)%0A%0A%20%20%20%20Density-based%20method%20that%20finds%20**local**%20anomalies.%20Critical%3A%20Uses%20**stratified-scaled**%20data%20(LOF%20is%20highly%20sensitive%20to%20relative%20distances).%0A%0A%20%20%20%20**Note%20on%20dimensionality%3A**%20With%2011%20features%2C%20we're%20in%20a%20moderate%20regime.%20LOF%20may%20struggle%20at%20higher%20dimensions%20due%20to%20the%20%22curse%20of%20dimensionality%22%20%E2%80%94%20distances%20become%20less%20meaningful%20as%20dimensions%20increase.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(LocalOutlierFactor)%3A%0A%20%20%20%20def%20detect_outliers_lof(X_scaled%2C%20contamination%3D0.05%2C%20n_neighbors%3D20)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Detect%20outliers%20using%20Local%20Outlier%20Factor.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20lof%20%3D%20LocalOutlierFactor(%0A%20%20%20%20%20%20%20%20%20%20%20%20n_neighbors%3Dn_neighbors%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20contamination%3Dcontamination%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20predictions%20%3D%20lof.fit_predict(X_scaled)%0A%20%20%20%20%20%20%20%20scores%20%3D%20-lof.negative_outlier_factor_%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20predictions%20%3D%3D%20-1%2C%20scores%0A%20%20%20%20return%20(detect_outliers_lof%2C)%0A%0A%0A%40app.cell%0Adef%20_(X_stratified_scaled%2C%20detect_outliers_lof)%3A%0A%20%20%20%20%23%20Use%20stratified-scaled%20data%0A%20%20%20%20lof_outliers%2C%20lof_scores%20%3D%20detect_outliers_lof(X_stratified_scaled)%0A%0A%20%20%20%20print(f%22LOF%20Results%20(contamination%3D5%25%2C%20n_neighbors%3D20%2C%20stratified%20scaling)%3A%22)%0A%20%20%20%20print(f%22%20%20Total%20outliers%3A%20%7Blof_outliers.sum()%3A%2C%7D%20(%7Blof_outliers.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20return%20(lof_outliers%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Method%205%3A%20Elliptic%20Envelope%0A%0A%20%20%20%20**%E2%9A%A0%EF%B8%8F%20Assumption%20Warning%3A**%20This%20method%20assumes%20multivariate%20Gaussian%2C%20which%20is%20**heavily%20violated**%20(skewness%20up%20to%205.4).%20We%20include%20it%20as%20a%20%22Gaussian%20baseline%22%20but%20**exclude%20it%20from%20the%20primary%20consensus**%20due%20to%20model%20mismatch.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(EllipticEnvelope)%3A%0A%20%20%20%20def%20detect_outliers_elliptic(X_scaled%2C%20contamination%3D0.05)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Detect%20outliers%20using%20Elliptic%20Envelope.%0A%20%20%20%20%20%20%20%20WARNING%3A%20Assumes%20multivariate%20Gaussian.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20envelope%20%3D%20EllipticEnvelope(%0A%20%20%20%20%20%20%20%20%20%20%20%20contamination%3Dcontamination%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3D42%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20predictions%20%3D%20envelope.fit_predict(X_scaled)%0A%20%20%20%20%20%20%20%20scores%20%3D%20envelope.decision_function(X_scaled)%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20predictions%20%3D%3D%20-1%2C%20scores%0A%20%20%20%20return%20(detect_outliers_elliptic%2C)%0A%0A%0A%40app.cell%0Adef%20_(X_stratified_scaled%2C%20detect_outliers_elliptic)%3A%0A%20%20%20%20elliptic_outliers%2C%20elliptic_scores%20%3D%20detect_outliers_elliptic(X_stratified_scaled)%0A%0A%20%20%20%20print(f%22Elliptic%20Envelope%20Results%20(contamination%3D5%25)%3A%22)%0A%20%20%20%20print(f%22%20%20Total%20outliers%3A%20%7Belliptic_outliers.sum()%3A%2C%7D%20(%7Belliptic_outliers.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%5Cn%E2%9A%A0%EF%B8%8F%20%20Gaussian%20assumption%20VIOLATED.%20Included%20as%20baseline%2C%20excluded%20from%20primary%20consensus.%22)%0A%20%20%20%20return%20(elliptic_outliers%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20The%20Big%20Comparison%0A%0A%20%20%20%20We%20create%20two%20consensus%20measures%3A%0A%20%20%20%201.%20**Primary%20(4%20methods)%3A**%20Robust%20Z-Score%2C%20IQR%2C%20Isolation%20Forest%2C%20LOF%20%E2%80%94%20all%20with%20defensible%20assumptions%0A%20%20%20%202.%20**Extended%20(5%20methods)%3A**%20Includes%20Elliptic%20Envelope%20for%20reference%0A%0A%20%20%20%20**Important%3A**%20Low%20agreement%20doesn't%20mean%20methods%20are%20%22wrong%22%20%E2%80%94%20they%20detect%20different%20anomaly%20types%3A%0A%20%20%20%20-%20Z-Score%2FIQR%3A%20Univariate%20extremes%20(far%20from%20feature%20median)%0A%20%20%20%20-%20Isolation%20Forest%3A%20Globally%20%22easy%20to%20isolate%22%20points%0A%20%20%20%20-%20LOF%3A%20Local%20density%20deviations%20(unusual%20relative%20to%20neighbors)%0A%20%20%20%20-%20Elliptic%20Envelope%3A%20Far%20from%20multivariate%20center%20(Mahalanobis%20distance)%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20elliptic_outliers%2C%0A%20%20%20%20iqr_outliers%2C%0A%20%20%20%20iso_outliers%2C%0A%20%20%20%20lof_outliers%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20zscore_outliers%2C%0A)%3A%0A%20%20%20%20%23%20PRIMARY%20COMPARISON%3A%204%20methods%20with%20defensible%20assumptions%0A%20%20%20%20comparison_primary%20%3D%20pd.DataFrame(%7B%0A%20%20%20%20%20%20%20%20'Robust%20Z-Score'%3A%20zscore_outliers%2C%0A%20%20%20%20%20%20%20%20'IQR'%3A%20iqr_outliers%2C%0A%20%20%20%20%20%20%20%20'Isolation%20Forest'%3A%20iso_outliers%2C%0A%20%20%20%20%20%20%20%20'LOF'%3A%20lof_outliers%2C%0A%20%20%20%20%7D)%0A%0A%20%20%20%20%23%20Extended%20(includes%20violated-assumption%20method)%0A%20%20%20%20comparison_extended%20%3D%20comparison_primary.copy()%0A%20%20%20%20comparison_extended%5B'Elliptic%20Envelope'%5D%20%3D%20elliptic_outliers%0A%0A%20%20%20%20%23%20Summary%20table%0A%20%20%20%20print(%22%3D%22%20*%2080)%0A%20%20%20%20print(%22RESULTS%3A%20OUTLIERS%20DETECTED%20BY%20EACH%20METHOD%22)%0A%20%20%20%20print(%22%3D%22%20*%2080)%0A%20%20%20%20print(f%22%7B'Method'%3A%3C25%7D%20%7B'Outliers'%3A%3E10%7D%20%7B'%25'%3A%3E8%7D%20%7B'Notes'%7D%22)%0A%20%20%20%20print(%22-%22%20*%2080)%0A%0A%20%20%20%20method_info%20%3D%20%7B%0A%20%20%20%20%20%20%20%20'Robust%20Z-Score'%3A%20('2%2B%20features%2C%20MAD-based%20%5BPRIMARY%5D'%2C%20zscore_outliers)%2C%0A%20%20%20%20%20%20%20%20'IQR'%3A%20('2%2B%20features%2C%201.5x%20%5BPRIMARY%5D'%2C%20iqr_outliers)%2C%0A%20%20%20%20%20%20%20%20'Isolation%20Forest'%3A%20('5%25%20contamination%2C%20stratified%20%5BPRIMARY%5D'%2C%20iso_outliers)%2C%0A%20%20%20%20%20%20%20%20'LOF'%3A%20('5%25%20contamination%2C%20stratified%20%5BPRIMARY%5D'%2C%20lof_outliers)%2C%0A%20%20%20%20%20%20%20%20'Elliptic%20Envelope'%3A%20('5%25%20contamination%20%E2%9A%A0%EF%B8%8F%20EXCLUDED%20from%20consensus'%2C%20elliptic_outliers)%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20for%20method%2C%20(note%2C%20mask)%20in%20method_info.items()%3A%0A%20%20%20%20%20%20%20%20count%20%3D%20mask.sum()%0A%20%20%20%20%20%20%20%20pct%20%3D%20mask.mean()%20*%20100%0A%20%20%20%20%20%20%20%20print(f%22%7Bmethod%3A%3C25%7D%20%7Bcount%3A%3E10%2C%7D%20%7Bpct%3A%3E7.1f%7D%25%20%20(%7Bnote%7D)%22)%0A%0A%20%20%20%20print(%22%3D%22%20*%2080)%0A%20%20%20%20return%20(comparison_primary%2C)%0A%0A%0A%40app.cell%0Adef%20_(comparison_primary%2C%20pd%2C%20plt%2C%20sns)%3A%0A%20%20%20%20%23%20Jaccard%20similarity%0A%20%20%20%20def%20jaccard_matrix(comparison_df)%3A%0A%20%20%20%20%20%20%20%20methods%20%3D%20comparison_df.columns%0A%20%20%20%20%20%20%20%20jaccard%20%3D%20pd.DataFrame(index%3Dmethods%2C%20columns%3Dmethods%2C%20dtype%3Dfloat)%0A%20%20%20%20%20%20%20%20for%20m1%20in%20methods%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20m2%20in%20methods%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20intersection%20%3D%20(comparison_df%5Bm1%5D%20%26%20comparison_df%5Bm2%5D).sum()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20union%20%3D%20(comparison_df%5Bm1%5D%20%7C%20comparison_df%5Bm2%5D).sum()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20jaccard.loc%5Bm1%2C%20m2%5D%20%3D%20intersection%20%2F%20union%20if%20union%20%3E%200%20else%200%0A%20%20%20%20%20%20%20%20return%20jaccard%0A%20%20%20%20jaccard%20%3D%20jaccard_matrix(comparison_primary)%0A%20%20%20%20fig%2C%20axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(14%2C%205))%0A%20%20%20%20ax1%20%3D%20axes%5B0%5D%0A%20%20%20%20counts%20%3D%20comparison_primary.sum()%0A%20%20%20%20bar_colors%20%3D%20%5B'steelblue'%2C%20'coral'%2C%20'forestgreen'%2C%20'purple'%5D%0A%20%20%20%20bars%20%3D%20ax1.bar(counts.index%2C%20counts.values%2C%20color%3Dbar_colors%2C%20edgecolor%3D'white')%0A%20%20%20%20ax1.set_ylabel('Number%20of%20Outliers')%0A%20%20%20%20ax1.set_title('Outliers%20Detected%20(Primary%204%20Methods)'%2C%20fontweight%3D'bold')%0A%20%20%20%20ax1.tick_params(axis%3D'x'%2C%20rotation%3D45)%0A%20%20%20%20ax2%20%3D%20axes%5B1%5D%0A%20%20%20%20sns.heatmap(jaccard.astype(float)%2C%20annot%3DTrue%2C%20fmt%3D'.2f'%2C%20cmap%3D'YlOrRd'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20vmin%3D0%2C%20vmax%3D1%2C%20ax%3Dax2%2C%20square%3DTrue)%0A%20%20%20%20ax2.set_title('Method%20Agreement%20(Jaccard%20Similarity)'%2C%20fontweight%3D'bold')%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20print(%22Low%20Jaccard%20(~0.1-0.3)%20reflects%20different%20anomaly%20types%2C%20not%20method%20failure.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comparison_primary%2C%20df)%3A%0A%20%20%20%20%23%20Consensus%20analysis%20(4%20primary%20methods)%0A%20%20%20%20votes_primary%20%3D%20comparison_primary.sum(axis%3D1)%0A%20%20%20%20print(%22%5Cn%22%20%2B%20%22%3D%22%20*%2070)%0A%20%20%20%20print(%22CONSENSUS%20(4%20PRIMARY%20METHODS)%22)%0A%20%20%20%20print(%22%3D%22%20*%2070)%0A%20%20%20%20for%20i%20in%20range(5)%3A%0A%20%20%20%20%20%20%20%20vote_count%20%3D%20(votes_primary%20%3D%3D%20i).sum()%0A%20%20%20%20%20%20%20%20vote_pct%20%3D%20vote_count%20%2F%20len(votes_primary)%20*%20100%0A%20%20%20%20%20%20%20%20bar%20%3D%20'%E2%96%88'%20*%20int(vote_pct%20%2F%202)%0A%20%20%20%20%20%20%20%20labels%20%3D%20%7B0%3A%20%22No%20methods%20flagged%22%2C%204%3A%20%22ALL%204%20methods%20flagged%22%7D%0A%20%20%20%20%20%20%20%20label%20%3D%20labels.get(i%2C%20f%22%7Bi%7D%20method(s)%20flagged%22)%0A%20%20%20%20%20%20%20%20print(f%22%20%20%7Blabel%3A%3C35%7D%20%7Bvote_count%3A%3E5%2C%7D%20(%7Bvote_pct%3A%3E5.1f%7D%25)%20%7Bbar%7D%22)%0A%20%20%20%20consensus_flagged%20%3D%20(votes_primary%20%3E%3D%203).sum()%0A%20%20%20%20unanimous%20%3D%20(votes_primary%20%3D%3D%204).sum()%0A%20%20%20%20print(f%22%5Cn%20%20%E2%86%92%20Consensus%20(3%2B%20methods)%3A%20%7Bconsensus_flagged%3A%2C%7D%20(%7Bconsensus_flagged%2Flen(df)*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(f%22%20%20%E2%86%92%20Unanimous%20(all%204)%3A%20%20%20%20%20%20%7Bunanimous%3A%2C%7D%20(%7Bunanimous%2Flen(df)*100%3A.1f%7D%25)%22)%0A%20%20%20%20print(%22%3D%22%20*%2070)%0A%20%20%20%20return%20unanimous%2C%20votes_primary%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Contamination%20Sensitivity%20Analysis%0A%0A%20%20%20%20The%20%60contamination%3D0.05%60%20is%20arbitrary.%20Let's%20test%20how%20stable%20the%20results%20are.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20X_stratified_scaled%2C%0A%20%20%20%20detect_outliers_isolation_forest%2C%0A%20%20%20%20detect_outliers_lof%2C%0A%20%20%20%20iso_outliers%2C%0A%20%20%20%20lof_outliers%2C%0A%20%20%20%20np%2C%0A)%3A%0A%20%20%20%20%23%20Sensitivity%20sweep%0A%20%20%20%20contamination_values%20%3D%20%5B0.01%2C%200.03%2C%200.05%2C%200.10%5D%0A%0A%20%20%20%20print(%22CONTAMINATION%20SENSITIVITY%22)%0A%20%20%20%20print(%22%3D%22%20*%2075)%0A%20%20%20%20print(f%22%7B'Contam'%3A%3C8%7D%20%7B'IF%20Count'%3A%3E10%7D%20%7B'LOF%20Count'%3A%3E10%7D%20%7B'IF%20Jaccard'%3A%3E12%7D%20%7B'LOF%20Jaccard'%3A%3E12%7D%22)%0A%20%20%20%20print(%22-%22%20*%2075)%0A%0A%20%20%20%20baseline_iso%20%3D%20set(np.where(iso_outliers)%5B0%5D)%0A%20%20%20%20baseline_lof%20%3D%20set(np.where(lof_outliers)%5B0%5D)%0A%0A%20%20%20%20for%20cont%20in%20contamination_values%3A%0A%20%20%20%20%20%20%20%20iso_test%2C%20_%20%3D%20detect_outliers_isolation_forest(X_stratified_scaled%2C%20contamination%3Dcont)%0A%20%20%20%20%20%20%20%20lof_test%2C%20_%20%3D%20detect_outliers_lof(X_stratified_scaled%2C%20contamination%3Dcont)%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20iso_set%20%3D%20set(np.where(iso_test)%5B0%5D)%0A%20%20%20%20%20%20%20%20lof_set%20%3D%20set(np.where(lof_test)%5B0%5D)%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%23%20Jaccard%20with%205%25%20baseline%0A%20%20%20%20%20%20%20%20iso_j%20%3D%20len(iso_set%20%26%20baseline_iso)%20%2F%20len(iso_set%20%7C%20baseline_iso)%20if%20iso_set%20%7C%20baseline_iso%20else%201.0%0A%20%20%20%20%20%20%20%20lof_j%20%3D%20len(lof_set%20%26%20baseline_lof)%20%2F%20len(lof_set%20%7C%20baseline_lof)%20if%20lof_set%20%7C%20baseline_lof%20else%201.0%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20marker%20%3D%20%22%20%E2%86%90%20baseline%22%20if%20cont%20%3D%3D%200.05%20else%20%22%22%0A%20%20%20%20%20%20%20%20print(f%22%7Bcont%3A%3C8%7D%20%7Biso_test.sum()%3A%3E10%2C%7D%20%7Blof_test.sum()%3A%3E10%2C%7D%20%7Biso_j%3A%3E12.2f%7D%20%7Blof_j%3A%3E12.2f%7D%7Bmarker%7D%22)%0A%0A%20%20%20%20print(%22%5Cn%E2%86%92%20Jaccard%20vs%205%25%20baseline%20shows%20how%20much%20the%20flagged%20set%20changes.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Mixture%20Analysis%3A%20Red%20vs%20White%20Wines%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20pd%2C%20stats%2C%20votes_primary)%3A%0A%20%20%20%20%23%20Add%20votes%20to%20dataframe%0A%20%20%20%20df%5B'outlier_votes'%5D%20%3D%20votes_primary%0A%0A%20%20%20%20print(%22OUTLIERS%20BY%20WINE%20TYPE%22)%0A%20%20%20%20print(%22%3D%22%20*%2060)%0A%0A%20%20%20%20for%20wine_type%20in%20%5B'red'%2C%20'white'%5D%3A%0A%20%20%20%20%20%20%20%20subset%20%3D%20df%5Bdf%5B'wine_type'%5D%20%3D%3D%20wine_type%5D%0A%20%20%20%20%20%20%20%20consensus_rate%20%3D%20(subset%5B'outlier_votes'%5D%20%3E%3D%203).mean()%20*%20100%0A%20%20%20%20%20%20%20%20avg_votes%20%3D%20subset%5B'outlier_votes'%5D.mean()%0A%20%20%20%20%20%20%20%20print(f%22%5Cn%7Bwine_type.upper()%7D%20wines%20(%7Blen(subset)%3A%2C%7D)%3A%22)%0A%20%20%20%20%20%20%20%20print(f%22%20%20Average%20methods%20flagging%3A%20%7Bavg_votes%3A.2f%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22%20%20Consensus%20outliers%20(3%2B)%3A%20%20%7Bconsensus_rate%3A.1f%7D%25%22)%0A%0A%20%20%20%20%23%20Statistical%20test%20with%20proper%20handling%0A%20%20%20%20contingency%20%3D%20pd.crosstab(df%5B'wine_type'%5D%2C%20df%5B'outlier_votes'%5D%20%3E%3D%203)%0A%20%20%20%20_%2C%20_%2C%20_%2C%20expected%20%3D%20stats.chi2_contingency(contingency)%0A%0A%20%20%20%20if%20expected.min()%20%3E%3D%205%3A%0A%20%20%20%20%20%20%20%20chi2%2C%20p_value%2C%20_%2C%20_%20%3D%20stats.chi2_contingency(contingency)%0A%20%20%20%20%20%20%20%20test_name%20%3D%20%22Chi-square%22%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_%2C%20p_value%20%3D%20stats.fisher_exact(contingency)%0A%20%20%20%20%20%20%20%20test_name%20%3D%20%22Fisher's%20exact%22%0A%0A%20%20%20%20print(f%22%5Cn%7Btest_name%7D%20test%20(wine%20type%20vs%20consensus%20outlier)%3A%20p%3D%7Bp_value%3A.4f%7D%22)%0A%20%20%20%20if%20p_value%20%3C%200.05%3A%0A%20%20%20%20%20%20%20%20print(f%22%20%20%E2%86%92%20Significant%20association.%20This%20could%20mean%3A%22)%0A%20%20%20%20%20%20%20%20print(f%22%20%20%20%20%20(a)%20Mixture%20effects%20confound%20combined%20analysis%2C%20OR%22)%0A%20%20%20%20%20%20%20%20print(f%22%20%20%20%20%20(b)%20One%20wine%20type%20genuinely%20has%20more%20extreme%20chemistry.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_stratified_scaled%2C%20detect_outliers_lof%2C%20df)%3A%0A%20%20%20%20%23%20LOF%20sensitivity%20to%20n_neighbors%20(for%20stratified%20analysis)%0A%20%20%20%20print(%22%5CnLOF%20n_neighbors%20SENSITIVITY%20(Red%20wines%20only)%22)%0A%20%20%20%20print(%22-%22%20*%2050)%0A%0A%20%20%20%20red_df%20%3D%20df%5Bdf%5B'wine_type'%5D%20%3D%3D%20'red'%5D%0A%20%20%20%20red_scaled%20%3D%20X_stratified_scaled.loc%5Bred_df.index%5D%0A%0A%20%20%20%20for%20k%20in%20%5B10%2C%2020%2C%2050%5D%3A%0A%20%20%20%20%20%20%20%20lof_k%2C%20_%20%3D%20detect_outliers_lof(red_scaled%2C%20n_neighbors%3Dk)%0A%20%20%20%20%20%20%20%20print(f%22%20%20n_neighbors%3D%7Bk%7D%3A%20%7Blof_k.sum()%7D%20outliers%20(%7Blof_k.mean()*100%3A.1f%7D%25)%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Quality%20Correlation%20Analysis%0A%0A%20%20%20%20The%20dataset%20includes%20expert%20quality%20ratings%20(3-9).%20We%20can%20check%20if%20outlier%20status%20**correlates**%20with%20extreme%20ratings.%0A%0A%20%20%20%20**Important%20caveat%3A**%20This%20is%20a%20correlation%20check%2C%20not%20validation.%20Finding%20outliers%20correlate%20with%20extreme%20quality%20suggests%20methods%20pick%20up%20physicochemical%20patterns%20experts%20also%20noticed%20%E2%80%94%20but%20it%20doesn't%20prove%20flagged%20points%20are%20%22exceptional%20wines%22%20vs%20measurement%20errors.%20A%20wine%20with%20%22outlying%22%20high%20volatile%20acidity%20is%20chemically%20an%20outlier%20that%20will%20almost%20certainly%20receive%20a%20low%20rating%20%E2%80%94%20the%20outlier%20status%20may%20be%20the%20**cause**%20of%20the%20extreme%20rating%2C%20not%20just%20coincidental.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df)%3A%0A%20%20%20%20print(%22OUTLIER%20STATUS%20vs%20WINE%20QUALITY%22)%0A%20%20%20%20print(%22%3D%22%20*%2060)%0A%0A%20%20%20%20quality_stats%20%3D%20df.groupby('quality').agg(%0A%20%20%20%20%20%20%20%20avg_votes%3D('outlier_votes'%2C%20'mean')%2C%0A%20%20%20%20%20%20%20%20count%3D('outlier_votes'%2C%20'size')%2C%0A%20%20%20%20%20%20%20%20consensus_pct%3D('outlier_votes'%2C%20lambda%20x%3A%20(x%20%3E%3D%203).mean()%20*%20100)%0A%20%20%20%20).round(2)%0A%0A%20%20%20%20print(quality_stats)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df)%3A%0A%20%20%20%20%23%20Statistical%20comparison%0A%20%20%20%20print(%22%5CnEXTREME%20vs%20NORMAL%20QUALITY%22)%0A%20%20%20%20print(%22%3D%22%20*%2060)%0A%0A%20%20%20%20extreme_mask%20%3D%20df%5B'quality'%5D.isin(%5B3%2C%204%2C%208%2C%209%5D)%0A%20%20%20%20extreme%20%3D%20df%5Bextreme_mask%5D%0A%20%20%20%20normal%20%3D%20df%5B~extreme_mask%5D%0A%0A%20%20%20%20print(f%22%5CnExtreme%20quality%20(3%2C%204%2C%208%2C%209)%3A%20%7Blen(extreme)%3A%2C%7D%20samples%22)%0A%20%20%20%20print(f%22%20%20Consensus%20outliers%20(3%2B)%3A%20%7B(extreme%5B'outlier_votes'%5D%20%3E%3D%203).mean()*100%3A.1f%7D%25%22)%0A%0A%20%20%20%20print(f%22%5CnNormal%20quality%20(5%2C%206%2C%207)%3A%20%7Blen(normal)%3A%2C%7D%20samples%22)%0A%20%20%20%20print(f%22%20%20Consensus%20outliers%20(3%2B)%3A%20%7B(normal%5B'outlier_votes'%5D%20%3E%3D%203).mean()*100%3A.1f%7D%25%22)%0A%0A%20%20%20%20ext_rate%20%3D%20(extreme%5B'outlier_votes'%5D%20%3E%3D%203).mean()%20*%20100%0A%20%20%20%20norm_rate%20%3D%20(normal%5B'outlier_votes'%5D%20%3E%3D%203).mean()%20*%20100%0A%20%20%20%20if%20norm_rate%20%3E%200%3A%0A%20%20%20%20%20%20%20%20print(f%22%5Cn%E2%86%92%20Extreme-quality%20wines%20are%20%7Bext_rate%2Fnorm_rate%3A.1f%7Dx%20more%20likely%20to%20be%20consensus%20outliers.%22)%0A%0A%20%20%20%20print(f%22%5CnInterpretation%3A%20This%20correlation%20suggests%20outlier%20detection%20picks%20up%22)%0A%20%20%20%20print(f%22physicochemical%20patterns%20associated%20with%20extreme%20ratings.%20However%3A%22)%0A%20%20%20%20print(f%22%20%20%E2%80%A2%20This%20is%20correlation%2C%20not%20ground%20truth%20validation%22)%0A%20%20%20%20print(f%22%20%20%E2%80%A2%20Extreme%20chemistry%20may%20CAUSE%20low%20ratings%20(not%20just%20correlate)%22)%0A%20%20%20%20print(f%22%20%20%E2%80%A2%20Domain%20expert%20review%20needed%20to%20distinguish%20'unusual'%20from%20'error'%22)%0A%20%20%20%20return%20ext_rate%2C%20norm_rate%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Summary%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20df%2C%0A%20%20%20%20ext_rate%2C%0A%20%20%20%20feature_cols%2C%0A%20%20%20%20highly_skewed%2C%0A%20%20%20%20iqr_aggressive%2C%0A%20%20%20%20iqr_outliers%2C%0A%20%20%20%20norm_rate%2C%0A%20%20%20%20red%2C%0A%20%20%20%20unanimous%2C%0A%20%20%20%20white%2C%0A%20%20%20%20zscore_aggressive%2C%0A%20%20%20%20zscore_outliers%2C%0A)%3A%0A%20%20%20%20print(%22%3D%22%20*%2075)%0A%20%20%20%20print(%22EXPERIMENT%20SUMMARY%22)%0A%20%20%20%20print(%22%3D%22%20*%2075)%0A%0A%20%20%20%20print(f%22%5CnDataset%3A%20%7Blen(df)%3A%2C%7D%20wines%20(%7Blen(red)%3A%2C%7D%20red%2C%20%7Blen(white)%3A%2C%7D%20white)%22)%0A%20%20%20%20print(f%22Features%3A%20%7Blen(feature_cols)%7D%20(moderate%20dimensionality)%22)%0A%20%20%20%20print(f%22Skewed%20features%3A%20%7Bhighly_skewed%7D%20of%20%7Blen(feature_cols)%7D%22)%0A%0A%20%20%20%20print(%22%5Cn%22%20%2B%20%22-%22%20*%2075)%0A%20%20%20%20print(%22KEY%20FINDINGS%3A%22)%0A%20%20%20%20print(%22-%22%20*%2075)%0A%20%20%20%20print(f%22%20%201.%20Multiple-testing%20matters%20(both%20methods%20show%20same%20pattern)%3A%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Z-Score%20(any%20feature)%3A%20%7Bzscore_aggressive.mean()*100%3A.0f%7D%25%20%20%E2%86%92%20(2%2B%20features)%3A%20%7Bzscore_outliers.mean()*100%3A.1f%7D%25%20%20(7x%20reduction)%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20IQR%20(any%20feature)%3A%20%20%20%20%20%7Biqr_aggressive.mean()*100%3A.0f%7D%25%20%20%E2%86%92%20(2%2B%20features)%3A%20%7Biqr_outliers.mean()*100%3A.1f%7D%25%20%20(3x%20reduction)%22)%0A%20%20%20%20print(f%22%22)%0A%20%20%20%20print(f%22%20%202.%20Methods%20detect%20different%20anomaly%20types%3A%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Only%20%7Bunanimous%7D%20samples%20(%7Bunanimous%2Flen(df)*100%3A.1f%7D%25)%20flagged%20by%20all%204%20primary%20methods%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Low%20Jaccard%20(~0.1-0.3)%20%3D%20complementary%20perspectives%2C%20not%20failure%22)%0A%20%20%20%20print(f%22%22)%0A%20%20%20%20print(f%22%20%203.%20Quality%20correlation%20(sanity%20check%2C%20not%20validation)%3A%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Extreme-quality%20wines%20~%7Bext_rate%2Fnorm_rate%3A.0f%7Dx%20more%20likely%20to%20be%20outliers%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20May%20reflect%20causation%20(bad%20chemistry%20%E2%86%92%20bad%20rating)%22)%0A%20%20%20%20print(f%22%22)%0A%20%20%20%20print(f%22%20%204.%20Methodological%20best%20practices%3A%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Robust%20Z-score%20(median%2FMAD)%20for%20skewed%20data%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20min_features%3D2%20to%20reduce%20multiple-testing%20inflation%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Stratified%20scaling%20for%20mixture%20populations%22)%0A%20%20%20%20print(f%22%20%20%20%20%20-%20Exclude%20methods%20with%20violated%20assumptions%20from%20consensus%22)%0A%20%20%20%20print(%22%3D%22%20*%2075)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20df%2C%0A%20%20%20%20elliptic_outliers%2C%0A%20%20%20%20iqr_outliers%2C%0A%20%20%20%20iso_outliers%2C%0A%20%20%20%20lof_outliers%2C%0A%20%20%20%20votes_primary%2C%0A%20%20%20%20zscore_outliers%2C%0A%20%20%20%20zscore_standard%2C%0A)%3A%0A%20%20%20%20%23%20Save%20results%20(BOTH%20Z-score%20variants)%0A%20%20%20%20results_df%20%3D%20df.copy()%0A%20%20%20%20results_df%5B'outlier_zscore_robust'%5D%20%3D%20zscore_outliers%0A%20%20%20%20results_df%5B'outlier_zscore_standard'%5D%20%3D%20zscore_standard%0A%20%20%20%20results_df%5B'outlier_iqr'%5D%20%3D%20iqr_outliers%0A%20%20%20%20results_df%5B'outlier_iforest'%5D%20%3D%20iso_outliers%0A%20%20%20%20results_df%5B'outlier_lof'%5D%20%3D%20lof_outliers%0A%20%20%20%20results_df%5B'outlier_elliptic'%5D%20%3D%20elliptic_outliers%0A%20%20%20%20results_df%5B'consensus_4methods'%5D%20%3D%20votes_primary%0A%0A%20%20%20%20results_df.to_csv('wine_outlier_detection_results.csv'%2C%20index%3DFalse)%0A%20%20%20%20print(%22Results%20saved%20with%20both%20Z-score%20variants%20and%204-method%20consensus.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20---%0A%0A%20%20%20%20%23%23%20Conclusion%0A%0A%20%20%20%20We%20tested%205%20outlier%20detection%20methods%20on%206%2C497%20wines.%20Key%20takeaways%3A%0A%0A%20%20%20%201.%20**Methods%20detect%20different%20things.**%20Low%20agreement%20(~10-30%25%20Jaccard)%20isn't%20failure%20%E2%80%94%20Z-Score%2FIQR%20find%20univariate%20extremes%2C%20LOF%20finds%20local%20density%20deviations%2C%20Isolation%20Forest%20finds%20globally%20%22different%22%20points.%20These%20are%20complementary.%0A%0A%20%20%20%202.%20**Methodology%20matters.**%20Using%20%60min_features%3D1%60%20inflated%20IQR%20to%2023%25%3B%20%60min_features%3D2%60%20gives%208%25.%20Stratified%20scaling%20prevents%20mixture%20confounds.%20Excluding%20methods%20with%20violated%20assumptions%20(Elliptic%20Envelope)%20improves%20consensus%20validity.%0A%0A%20%20%20%203.%20**Outliers%20correlated%20with%20extreme%20quality.**%20This%20is%20a%20sanity%20check%20suggesting%20methods%20capture%20real%20patterns%20%E2%80%94%20but%20it's%20not%20ground%20truth%20validation.%20Extreme%20chemistry%20may%20*cause*%20low%20ratings%20(causation%2C%20not%20just%20correlation).%0A%0A%20%20%20%204.%20**Consensus%20is%20more%20robust.**%20Samples%20flagged%20by%203%2B%20methods%20are%20unusual%20across%20multiple%20definitions%20of%20%22unusual.%22%0A%0A%20%20%20%20**Core%20lesson%3A**%20%22Should%20I%20remove%20outliers%3F%22%20is%20the%20wrong%20question.%20Ask%3A%20%22What%20kind%20of%20unusual%20am%20I%20looking%20for%3F%22%20and%20%22Are%20these%20errors%20or%20just%20rare%3F%22%20The%20answer%20determines%20both%20method%20choice%20and%20whether%20removal%20is%20appropriate.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
1c7dcb7ecf4c5bee085ba566cf07b697a33d4ec1ba10283bb19b80405ca325d0