Support Vector Machines
Exercise
Fraud in Wine
Wine fraud relates to the commercial aspects of wine. The most prevalent type of fraud is one where wines are adulterated, usually with the addition of cheaper products (e.g. juices) and sometimes with harmful chemicals and sweeteners (compensating for color or flavor).
Counterfeiting and the relabelling of inferior and cheaper wines to more expensive brands is another common type of wine fraud.

Project Goals
A distribution company that was recently a victim of fraud has completed an audit of various samples of wine through the use of chemical analysis on samples. The distribution company specializes in exporting extremely high quality, expensive wines, but was defrauded by a supplier who was attempting to pass off cheap, low quality wine as higher grade wine. The distribution company has hired you to attempt to create a machine learning model that can help detect low quality (a.k.a "fraud") wine samples. They want to know if it is even possible to detect such a difference.
Data Source: P. Cortez, A. Cerdeira, F. Almeida, T. Matos and J. Reis. Modeling wine preferences by data mining from physicochemical properties. In Decision Support Systems, Elsevier, 47(4):547-553, 2009.
TASK: Your overall goal is to use the wine dataset shown below to develop a machine learning model that attempts to predict if a wine is "Legit" or "Fraud" based on various chemical features. Complete the tasks below to follow along with the project.
Complete the Tasks in bold
TASK: Run the cells below to import the libraries and load the dataset.
df = pd.read_csv("../DATA/wine_fraud.csv")df.head()fixed acidity
volatile acidity
citric acid
residual sugar
chlorides78 more lines
free sulfur dioxide
total sulfur dioxide
density
pH
sulphates
alcohol
quality
type
0
7.4
0.70
0.00
1.9
0.076
11.0
34.0
0.9978
3.51
0.56
9.4
Legit
red
1
7.8
0.88
0.00
2.6
0.098
25.0
67.0
0.9968
3.20
0.68
9.8
Legit
red
2
7.8
0.76
0.04
2.3
0.092
15.0
54.0
0.9970
3.26
0.65
9.8
Legit
red
3
11.2
0.28
0.56
1.9
0.075
17.0
60.0
0.9980
3.16
0.58
9.8
Legit
red
4
7.4
0.70
0.00
1.9
0.076
11.0
34.0
0.9978
3.51
0.56
9.4
Legit
redTASK: What are the unique variables in the target column we are trying to predict (quality)?
df.columnsIndex(['fixed acidity', 'volatile acidity', 'citric acid', 'residual sugar',
'chlorides', 'free sulfur dioxide', 'total sulfur dioxide', 'density',
'pH', 'sulphates', 'alcohol', 'quality', 'type'],
dtype='object')df.quality.unique()array(['Legit', 'Fraud'], dtype=object)TASK: Create a countplot that displays the count per category of Legit vs Fraud. Is the label/target balanced or unbalanced?
sns.countplot(x='quality',hue='quality',data=df)<Axes: xlabel='quality', ylabel='count'>
*TASK: Let's find out if there is a difference between red and white wine when it comes to fraud. Create a countplot that has the wine type on the x axis with the hue separating columns by Fraud vs Legit.*
sns.countplot(x='type',hue='quality',data=df)<Axes: xlabel='type', ylabel='count'>
TASK: What percentage of red wines are Fraud? What percentage of white wines are fraud?
reds = df[df['type']=='red']
whites = df[df['type']=='white']len(reds[reds['quality']=='Fraud']) / len(reds) * 1003.9399624765478425len(whites[whites['quality']=='Fraud']) / len(whites) * 1003.7362188648427925TASK: Calculate the correlation between the various features and the "quality" column. To do this you may need to map the column to 0 and 1 instead of a string.
df['Fraud']= df['quality'].map({'Legit':0,'Fraud':1})df.corr(numeric_only=True)['Fraud']fixed acidity 0.021794
volatile acidity 0.151228
citric acid -0.061789
residual sugar -0.048756
chlorides 0.0344998 more lines
free sulfur dioxide -0.085204
total sulfur dioxide -0.035252
density 0.016351
pH 0.020107
sulphates -0.034046
alcohol -0.051141
Fraud 1.000000
Name: Fraud, dtype: float64TASK: Create a bar plot of the correlation values to Fraudlent wine.
df.corr(numeric_only=True)['Fraud'][:-1].sort_values().plot(kind='bar')<Axes: >
TASK: Create a clustermap with seaborn to explore the relationships between variables.
sns.clustermap(df.corr(numeric_only=True) ,annot=True)<seaborn.matrix.ClusterGrid at 0x126aa3f80>
Machine Learning Model
TASK: Convert the categorical column "type" from a string or "red" or "white" to dummy variables:
whites = df[df['type']=='white']
reds = df[df['type']=='red']df['type'] = pd.get_dummies(df['type'],drop_first=True)
df = df.drop('Fraud',axis=1)TASK: Separate out the data into X features and y target label ("quality" column)
X = df.drop('quality',axis=1)
y = df['quality']TASK: Perform a Train|Test split on the data, with a 10% test size. Note: The solution uses a random state of 101
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,random_state=101)TASK: Scale the X train and X test data.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaled_X_train = scaler.fit_transform(X_train)
scaled_X_test = scaler.transform(X_test)ValueError: could not convert string to float: 'red'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/var/folders/41/x856_7w17ss36djfjfjccwcr0000gn/T/ipykernel_54804/2359573251.py in ?()
1 from sklearn.preprocessing import StandardScaler89 more lines
2
3 scaler = StandardScaler()
4
----> 5 scaled_X_train = scaler.fit_transform(X_train)
6 scaled_X_test = scaler.transform(X_test)
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/_set_output.py in ?(self, X, *args, **kwargs)
314 @wraps(f)
315 def wrapped(self, X, *args, **kwargs):
--> 316 data_to_wrap = f(self, X, *args, **kwargs)
317 if isinstance(data_to_wrap, tuple):
318 # only wrap the first output for cross decomposition
319 return_tuple = (
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py in ?(self, X, y, **fit_params)
890 )
891
892 if y is None:
893 # fit method of arity 1 (unsupervised transformation)
--> 894 return self.fit(X, **fit_params).transform(X)
895 else:
896 # fit method of arity 2 (supervised transformation)
897 return self.fit(X, y, **fit_params).transform(X)
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/preprocessing/_data.py in ?(self, X, y, sample_weight)
903 Fitted scaler.
904 """
905 # Reset internal state before fitting
906 self._reset()
--> 907 return self.partial_fit(X, y, sample_weight)
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py in ?(estimator, *args, **kwargs)
1361 skip_parameter_validation=(
1362 prefer_skip_nested_validation or global_skip_validation
1363 )
1364 ):
-> 1365 return fit_method(estimator, *args, **kwargs)
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/preprocessing/_data.py in ?(self, X, y, sample_weight)
939 self : object
940 Fitted scaler.
941 """
942 first_call = not hasattr(self, "n_samples_seen_")
--> 943 X = validate_data(
944 self,
945 X,
946 accept_sparse=("csr", "csc"),
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/validation.py in ?(_estimator, X, y, reset, validate_separately, skip_check_array, **check_params)
2950 out = y
2951 else:
2952 out = X, y
2953 elif not no_val_X and no_val_y:
-> 2954 out = check_array(X, input_name="X", **check_params)
2955 elif no_val_X and not no_val_y:
2956 out = _check_y(y, **check_params)
2957 else:
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/validation.py in ?(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_writeable, force_all_finite, ensure_all_finite, ensure_non_negative, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)
1050 )
1051 array = xp.astype(array, dtype, copy=False)
1052 else:
1053 array = _asarray_with_order(array, order=order, dtype=dtype, xp=xp)
-> 1054 except ComplexWarning as complex_warning:
1055 raise ValueError(
1056 "Complex data not supported\n{}\n".format(array)
1057 ) from complex_warning
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/_array_api.py in ?(array, dtype, order, copy, xp, device)
753 # Use NumPy API to support order
754 if copy is True:
755 array = numpy.array(array, order=order, dtype=dtype)
756 else:
--> 757 array = numpy.asarray(array, order=order, dtype=dtype)
758
759 # At this point array is a NumPy ndarray. We convert it to an array
760 # container that is consistent with the input's namespace.
~/.pyenv/versions/3.12.6/lib/python3.12/site-packages/pandas/core/generic.py in ?(self, dtype, copy)
2149 def __array__(
2150 self, dtype: npt.DTypeLike | None = None, copy: bool_t | None = None
2151 ) -> np.ndarray:
2152 values = self._values
-> 2153 arr = np.asarray(values, dtype=dtype)
2154 if (
2155 astype_is_view(values.dtype, arr.dtype)
2156 and using_copy_on_write()
ValueError: could not convert string to float: 'red'TASK: Create an instance of a Support Vector Machine classifier. Previously we have left this model "blank", (e.g. with no parameters). However, we already know that the classes are unbalanced, in an attempt to help alleviate this issue, we can automatically adjust weights inversely proportional to class frequencies in the input data with a argument call in the SVC() call. Check out the documentation for SVC online and look up what the argument\parameter is.
from sklearn.svm import SVC
model = SVC()TASK: Use a GridSearchCV to run a grid search for the best C and gamma parameters.
from sklearn.model_selection import GridSearchCV
param_grid = {'C':[0.1,1,10,100],'gamma':['scaler', 'auto']}
grid = GridSearchCV(model,param_grid)grid.fit(X_train,y_train)/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/model_selection/_validation.py:516: FitFailedWarning:
20 fits failed out of a total of 40.
The score on these train-test partitions for these parameters will be set to nan.
If these failures are not expected, you can try to debug them by setting error_score='raise'.
18 more lines
Below are more details about the failures:
--------------------------------------------------------------------------------
20 fits failed with the following error:
Traceback (most recent call last):
File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/model_selection/_validation.py", line 859, in _fit_and_score
estimator.fit(X_train, y_train, **fit_params)
File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py", line 1358, in wrapper
estimator._validate_params()
File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/base.py", line 471, in _validate_params
validate_parameter_constraints(
File "/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/utils/_param_validation.py", line 98, in validate_parameter_constraints
raise InvalidParameterError(
sklearn.utils._param_validation.InvalidParameterError: The 'gamma' parameter of SVC must be a str among {'auto', 'scale'} or a float in the range [0.0, inf). Got 'scaler' instead.
warnings.warn(some_fits_failed_message, FitFailedWarning)
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/model_selection/_search.py:1135: UserWarning: One or more of the test scores are non-finite: [ nan 0.96254495 nan 0.96237386 nan 0.96220292
nan 0.95399311]
warnings.warn(GridSearchCV(estimator=SVC(),
param_grid={'C': [0.1, 1, 10, 100], 'gamma': ['scaler', 'auto']})In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV
?Documentation for GridSearchCViFitted56 more lines
Parameters
estimator
SVC()
param_grid
{'C': [0.1, 1, ...], 'gamma': ['scaler', 'auto']}
scoring
None
n_jobs
None
refit
True
cv
None
verbose
0
pre_dispatch
'2*n_jobs'
error_score
nan
return_train_score
False
best_estimator_: SVC
SVC(C=0.1, gamma='auto')
SVC
?Documentation for SVC
Parameters
C
0.1
kernel
'rbf'
degree
3
gamma
'auto'
coef0
0.0
shrinking
True
probability
False
tol
0.001
cache_size
200
class_weight
None
verbose
False
max_iter
-1
decision_function_shape
'ovr'
break_ties
False
random_state
Nonegrid.best_params_{'C': 0.1, 'gamma': 'auto'}TASK: Display the confusion matrix and classification report for your model.
from sklearn.metrics import confusion_matrix,classification_report
pred = grid.predict(X_test)
print(confusion_matrix(y_test,pred))[[ 0 27]
[ 0 623]]print(classification_report(y_test,pred)) precision recall f1-score support
Fraud 0.00 0.00 0.00 27
Legit 0.96 1.00 0.98 623
3 more lines
accuracy 0.96 650
macro avg 0.48 0.50 0.49 650
weighted avg 0.92 0.96 0.94 650/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/metrics/_classification.py:1731: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
_warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/metrics/_classification.py:1731: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
_warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
/Users/drippy/.pyenv/versions/3.12.6/lib/python3.12/site-packages/sklearn/metrics/_classification.py:1731: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.1 more line
_warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_estimator(grid, X_test, y_test)<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x12648c6e0>
TASK: Finally, think about how well this model performed, would you suggest using it? Realistically will this work?
# ANSWER: View the solutions video for full discussion on this.