Why does modified z-score not pick up an obvious outlier?

looking to draw on some of your wisdom around modified z-scores as used for detecting outliers.

As far as I can tell from my research, when a distribution might not be normal (e.g. skewed), a modified z-score is a better indicator of outliers than z-score itself. This is because instead of mean it uses median which is a robust estimator of central tendancy if the distribution is not known to be normal.

I am testing both of these plus some other outlier detection algorithms against a list of values where I know one value is an extreme outlier. To help me I created a small python program that calculates both z-scores and modified z-scores against that list and then uses that to check whether any of the items in the list look like outliers. Basically, I was checking those algorithms would successfully detect the extreme outlier I know is there.

To check the distribution of my data the program creates a box plot, the outlier (value = 200) is clearly evident. For reference, the median of this dataset is 58.

The modified z-score for the outlier value of 200 is only 2.81 and this is substantially lower than the 3.5 for consideration as an outlier, hence it does not get labelled as an outlier. FYI, I used 3.5 because that seems to the most recommended value for the cut-off.

The z-score for the outlier value of 200 is 3.40 and this is above the 3.0 for consideration as an outlier, hence it does get labelled as an outlier. FYI, I used 3.0 as the cut-off as that seemed most popular.

My question is, why does the z-score algorithm detect the outlier in my datasets when the modified z-score algorithm does not? This seems counter-intuitive to me, especially as the outlier is obvious from the box plot.

Here is my python in case that is where I have made an error:

import matplotlib as plt
import numpy as np
from scipy.stats import zscore

def z_score_mod(obs):
# modified z-score = 0.6745(xi – x̃) / MAD
med = np.median(obs)
med_abs_dev = np.median(np.abs(obs - med))
z_score_mod = 0.6745 * ((obs - med) / med_abs_dev)
return z_score_mod

# list of observations with reasonably large outlier value = 200 and index = 13
list_of_obs = [58,71,11,18,90,97,15,53,39,22,62,51,10,200,20,64,94,71,73,18,95,96,92,38,26]

# Convert list of observations to numpy array
array_of_obs = np.array(list_of_obs)

# Create box plot to show the outlier
plt.pyplot.boxplot(array_of_obs)

median = np.median(array_of_obs)

# Calculate modified z-score for each array item
array_of_z_score_mod = z_score_mod(array_of_obs)

# For the modified z-scores generated, determine if any are outliers
array_of_outlier_evals = abs(array_of_z_score_mod) > 3.5

# Is the observation value = 200 at index = 13 an outlier?
print('\r')
print(f'Observation at index position 13 = {list_of_obs[13]}')
print(f'Modified z-score of value = {array_of_z_score_mod[13]:.2f}')
print(f'At modified z-score threshold 3.5 is value an outlier : {array_of_outlier_evals[13]}')

# Calculate z-score for each array item
array_of_z_score = zscore(list_of_obs)

# For the z-scores generated, determine if any are outliers
array_of_outlier_evals_2 = abs(array_of_z_score) > 3

# Is the observation value = 200 at index = 13 an outlier?
print('\r')
print(f'Observation at index position 13 = {list_of_obs[13]}')
print(f'z-score of value = {array_of_z_score[13]:.2f}')
print(f'At z-score threshold 3.0 is value an outlier : {array_of_outlier_evals_2[13]}')

• Can you edit the post to include a formula for the modified z-score? Commented Aug 2 at 17:42
• There are better methods to flag outlying data. They vary depending on how many outliers you need to find, in which direction(s), on the purpose of the flagging, and so on. One principle they have in common is to use estimates of location and spread that do not depend on the possible outliers themselves. You might therefore be better off researching these methods rather than pursuing your current approach.
– whuber
Commented Aug 2 at 17:51
• @whuber Median and MAD do not depend on the outlier in a problematic way here - that's not the problem with modified z-scores in this situation. In fact in the robustness literature outlier identification based on median/MAD is something of a gold standard. Commented Aug 2 at 20:21
• @Christian Not quite. What you say is generally true for sufficiently large datasets, but for the smallish ones--a handful to at most a few hundred univariate observations--it doesn't apply as well as we might think. See the chapter on outliers in Madansky's book Prescriptions for Working Statisticians for a summary from a third of a century ago or Vic Barnett's text on outliers from before then.
– whuber
Commented Aug 2 at 20:57
• Are these real data? Is there a reason the values are integer? What constitutes an outlier depends on the type of process. For example, on a multiplicative scale, $200$ does not seem very far from the rest at all. Commented Aug 3 at 4:49