Statistics Machine Learning Python Draft
Statistics Machine Learning Python Draft
Python
Release 0.1
2 Python language 5
2.1 Set up your programming environment using Anaconda . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Import libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Data types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.5 Comparisons and boolean operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.6 Conditional statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.7 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.8 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.9 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.10 Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.11 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.12 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.13 Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.14 List comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.15 Exceptions handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.16 Basic operating system interfaces (os) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.17 Object Oriented Programing (OOP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.18 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
i
4.6 Rows selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.7 Rows selction / filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.8 Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.9 Reshaping by pivoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.10 Quality control: duplicate data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.11 Quality control: missing data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.12 Rename values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.13 Dealing with outliers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.14 Groupby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.15 File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.16 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6 Univariate statistics 45
6.1 Estimators of the main statistical measures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.2 Main distributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.3 Testing pairwise associations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.4 Non-parametric test of pairwise associations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.5 Linear model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.6 Linear model with statsmodels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.7 Multiple comparisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.8 Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7 Multivariate statistics 73
7.1 Linear Algebra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.2 Mean vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.3 Covariance matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.4 Precision matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.5 Mahalanobis distance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.6 Multivariate normal distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
7.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9 Clustering 99
9.1 K-means clustering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
9.2 Hierarchical clustering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
9.3 Gaussian mixture models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.4 Model selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
ii
10.3 Overfitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
10.4 Ridge regression (2 -regularization) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.5 Lasso regression (1 -regularization) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
10.6 Elastic-net regression (2 -1 -regularization) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
iii
iv
CHAPTER
ONE
1
Statistics and Machine Learning in Python, Release 0.1
Linear model.
Non parametric statistics.
Linear algebra: matrix operations, inversion, eigenvalues.
TWO
PYTHON LANGUAGE
bash Anaconda2-2.4.0-Linux-x86_64.sh
Python 3:
bash Anaconda3-2.4.1-Linux-x86_64.sh
export PATH="${HOME}/anaconda2/bin:$PATH"
Python 3:
export PATH="${HOME}/anaconda3/bin:$PATH"
Using pip:
Optional:
5
Statistics and Machine Learning in Python, Release 0.1
Import libraries
# import a function
from math import sqrt
sqrt(25) # no longer have to reference the module
# define an alias
import numpy as np
Data types
True
Math
# basic operations
10 + 4 # add (returns 14)
10 - 4 # subtract (returns 6)
10 * 4 # multiply (returns 40)
10 ** 4 # exponent (returns 10000)
10 / 4 # divide (returns 2 because both types are 'int')
10 / float(4) # divide (returns 2.5)
5 % 4 # modulo (returns 1) - also known as the remainder
# force '/' in Python 2.x to perform 'true division' (unnecessary in Python 3.x)
from __future__ import division
10 / 4 # true division (returns 2.5)
10 // 4 # floor division (returns 2)
True
Conditional statements
x = 3
# if statement
2.4. Math 7
Statistics and Machine Learning in Python, Release 0.1
if x > 0:
print('positive')
# if/else statement
if x > 0:
print('positive')
else:
print('zero or negative')
# if/elif/else statement
if x > 0:
print('positive')
elif x == 0:
print('zero')
else:
print('negative')
positive
positive
positive
positive
'positive'
Lists
Different objects categorized along a certain ordered sequence, lists are ordered, iterable, mutable (adding or removing
objects changes the list size), can contain multiple data types
# create an empty list (two ways)
empty_list = []
empty_list = list()
# create a list
simpsons = ['homer', 'marge', 'bart']
# examine a list
simpsons[0] # print element 0 ('homer')
len(simpsons) # returns the length (3)
# sort a list in place (modifies but does not return the list)
simpsons.sort()
simpsons.sort(reverse=True) # sort in reverse
simpsons.sort(key=len) # sort by a key
# return a sorted list (but does not modify the original list)
sorted(simpsons)
sorted(simpsons, reverse=True)
sorted(simpsons, key=len)
# examine objects
id(num) == id(same_num) # returns True
id(num) == id(new_num) # returns False
num is same_num # returns True
num is new_num # returns False
num == same_num # returns True
num == new_num # returns True (their contents are equivalent)
# conatenate +, replicate *
[1, 2, 3] + [4, 5, 6]
["a"] * 2 + ["b"] * 3
2.7. Lists 9
Statistics and Machine Learning in Python, Release 0.1
Tuples
Like lists, but their size cannot change: ordered, iterable, immutable, can contain multiple data types
# create a tuple
digits = (0, 1, 'two') # create a tuple directly
digits = tuple([0, 1, 'two']) # create a tuple from a list
zero = (0,) # trailing comma is required to indicate it's a tuple
# examine a tuple
digits[2] # returns 'two'
len(digits) # returns 3
digits.count(0) # counts the number of instances of that value (1)
digits.index(1) # returns the index of the first instance of that value (1)
# concatenate tuples
digits = digits + (3, 4)
# create a single tuple with elements repeated (also works with lists)
(3, 4) * 2 # returns (3, 4, 3, 4)
# tuple unpacking
bart = ('male', 10, 'simpson') # create a tuple
Strings
# examine a string
s[0] # returns 'I'
len(s) # returns 10
s.find('like') # returns index of first occurrence (2), but doesn't support regex
s.find('hate') # returns -1 since not found
s.replace('like','love') # replaces all instances of 'like' with 'love'
# concatenate strings
s3 = 'The meaning of life is'
s4 = '42'
s3 + ' ' + s4 # returns 'The meaning of life is 42'
s3 + ' ' + str(42) # same thing
# string formatting
# more examples: http://mkaz.com/2012/10/10/python-string-format/
'pi is {:.2f}'.format(3.14159) # returns 'pi is 3.14'
first line
second line
first linenfirst line
Dictionaries
Dictionaries are structures which can contain multiple data types, and is ordered with key-value pairs: for each (unique)
key, the dictionary outputs one value. Keys can be strings, numbers, or tuples, while the corresponding values can be
any Python object. Dictionaries are: unordered, iterable, mutable
2.10. Dictionaries 11
Statistics and Machine Learning in Python, Release 0.1
family = dict(list_of_tuples)
# examine a dictionary
family['dad'] # returns 'homer'
len(family) # returns 3
family.keys() # returns list: ['dad', 'mom', 'size']
family.values() # returns list: ['homer', 'marge', 6]
family.items() # returns list of tuples:
# [('dad', 'homer'), ('mom', 'marge'), ('size', 6)]
'mom' in family # returns True
'marge' in family # returns False (only checks keys)
Error 'grandma'
Sets
Like dictionaries, but with unique keys only (no corresponding values). They are: unordered, iterable, mutable, can
contain multiple data types made up of unique elements (strings, numbers, or tuples)
# create an empty set
empty_set = set()
# create a set
languages = {'python', 'r', 'java'} # create a set directly
snakes = set(['cobra', 'viper', 'python']) # create a set from a list
# examine a set
len(languages) # returns 3
'python' in languages # returns True
# set operations
languages & snakes # returns intersection: {'python'}
languages | snakes # returns union: {'cobra', 'r', 'java', 'viper', 'python'}
languages - snakes # returns set difference: {'r', 'java'}
snakes - languages # returns set difference: {'cobra', 'viper'}
except KeyError as e:
print("Error", e)
languages.discard('c') # removes an element if present, but ignored otherwise
languages.pop() # removes and returns an arbitrary element
languages.clear() # removes all elements
languages.update('go', 'spark') # add multiple elements (can also pass a list or set)
Error 'c'
[0, 1, 2, 9]
Functions
Functions are sets of instructions launched when called upon, they can have multiple input values and a return value
# define a function with no arguments and no return values
def print_text():
print('this is text')
2.12. Functions 13
Statistics and Machine Learning in Python, Release 0.1
# default arguments
def power_this(x, power=2):
return x ** power
power_this(2) # 4
power_this(2, 3) # 8
# return values can be assigned into multiple variables using tuple unpacking
min_num, max_num = min_max(nums) # min_num = 1, max_num = 3
this is text
3
3
Loops
Loops are a set of instructions which repeat until termination conditions are met. This can include iterating through
all values in an object, go through a range of values, etc
# range returns a list of integers
range(0, 3) # returns [0, 1, 2]: includes first value but excludes second value
range(3) # same thing: starting at zero is the default
range(0, 5, 2) # returns [0, 2, 4]: third argument specifies the 'stride'
# use range when iterating over a large sequence to avoid actually creating the
integer list in memory
for i in range(10**6):
pass
# use enumerate if you need to access the index value within the loop
for index, fruit in enumerate(fruits):
print(index, fruit)
# for/else loop
for fruit in fruits:
if fruit == 'banana':
print("Found the banana!")
break # exit the loop and skip the 'else' block
else:
# this block executes ONLY if the for loop completes without hitting 'break'
print("Can't find the banana")
# while loop
count = 0
while count < 5:
print("This will print 5 times")
count += 1 # equivalent to 'count = count + 1'
APPLE
BANANA
CHERRY
APPLE
BANANA
CHERRY
mom marge
dad homer
size 6
0 apple
1 banana
2 cherry
Found the banana!
This will print 5 times
This will print 5 times
This will print 5 times
This will print 5 times
This will print 5 times
List comprehensions
Process which affects whole lists without iterating through loops. For more: http://python-3-patterns-idioms-test.
readthedocs.io/en/latest/Comprehensions.html
cubes.append(num**3)
# set comprehension
fruits = ['apple', 'banana', 'cherry']
unique_lengths = {len(fruit) for fruit in fruits} # {5, 6}
# dictionary comprehension
fruit_lengths = {fruit:len(fruit) for fruit in fruits} # {'apple': 5,
'banana': 6, 'cherry': 6}
Exceptions handling
key = 'c'
try:
dct[key]
except:
print(dct)
import os
import tempfile
tmpdir = tempfile.gettempdir()
# list containing the names of the entries in the directory given by path.
os.listdir(tmpdir)
# Join paths
mytmpdir = os.path.join(tmpdir, "foobar")
# Create a directory
if not os.path.exists(mytmpdir):
os.mkdir(mytmpdir)
# Write
lines = ["Dans python tout est bon", "Enfin, presque"]
# Read
## read one line at a time (entire file does not have to fit into memory)
f = open(filename, "r")
f.readline() # one string per line (including newlines)
f.readline() # next line
f.close()
## read one line at a time (entire file does not have to fit into memory)
f = open(filename, 'r')
f.readline() # one string per line (including newlines)
f.readline() # next line
f.close()
## use list comprehension to duplicate readlines without reading entire file at once
f = open(filename, 'r')
[line for line in f]
f.close()
---------------------------------------------------------------------------
<ipython-input-15-6c4c2de1560a> in <module>()
27
28 ## write line by line
---> 29 fd = open(filename, "w")
30 fd.write(lines[0] + "\n")
31 fd.write(lines[1]+ "\n")
Sources
http://python-textbok.readthedocs.org/en/latest/Object_Oriented_Programming.html
Principles
Encapsulate data (attributes) and code (methods) into objects.
Class = template or blueprint that can be used to create objects.
An object is a specific instance of a class.
Inheritance: OOP allows classes to inherit commonly used state and behaviour from other classes. Reduce
code duplication
Polymorphism: (usually obtained through polymorphism) calling code is agnostic as to whether an object
belongs to a parent class or one of its descendants (abstraction, modularity). The same method called on 2
objects of 2 different classes will behave differently.
import math
class Shape2D:
def area(self):
raise NotImplementedError()
# Inheritance + Encapsulation
class Square(Shape2D):
def __init__(self, width):
self.width = width
def area(self):
return self.width ** 2
class Disk(Shape2D):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
# Polymorphism
print([s.area() for s in shapes])
s = Shape2D()
try:
s.area()
except NotImplementedError as e:
print("NotImplementedError")
Exercises
Exercise 1: functions
Create a function that acts as a simple calulator If the operation is not specified, default to addition If the operation is
misspecified, return an prompt message Ex: calc(4,5,"multiply") returns 20 Ex: calc(3,5) returns 8 Ex:
calc(1,2,"something") returns error message
Given a list of numbers, return a list where all adjacent duplicate elements have been reduced to a single element. Ex:
[1,2,2,3,2] returns [1,2,3,2]. You may create a new list or modify the passed in list.
Remove all duplicate values (adjacent or not) Ex: [1,2,2,3,2] returns [1,2,3]
Copy/past the bsd 4 clause license into a text file. Read, the file (assuming this file could be huge) and count the
occurrences of each word within the file. Words are separated by whitespace or new line characters.
2.18. Exercises 19
Statistics and Machine Learning in Python, Release 0.1
Exercise 4: OOP
1. Create a class Employee with 2 attributes provided in the constructor: name, years_of_service. With
one method salary with is obtained by 1500 + 100 * years_of_service.
2. Create a subclass Manager which redefine salary method 2500 + 120 * years_of_service.
3. Create a small dictionary-nosed database where the key is the employees name. Populate the database with:
samples = Employee(lucy, 3), Employee(john, 1), Manager(julie, 10), Manager(paul, 3)
4. Return a table of made name, salary rows, i.e. a list of list [[name, salary]]
5. Compute the average salary
THREE
NumPy is an extension to the Python programming language, adding support for large, multi-dimensional (numerical)
arrays and matrices, along with a large library of high-level mathematical functions to operate on these arrays.
Sources:
Kevin Markham: https://github.com/justmarkham
Create arrays
# examining arrays
arr1.dtype # float64
arr2.dtype # int32
arr2.ndim # 2
arr2.shape # (2, 4) - axis 0 is rows, axis 1 is columns
arr2.size # 8 - total number of elements
len(arr2) # 2 - size of first dimension (aka axis)
21
Statistics and Machine Learning in Python, Release 0.1
Reshaping
# Add an axis
a = np.array([0, 1])
a_col = a[:, np.newaxis]
# array([[0],
# [1]])
# Transpose
a_col.T
#array([[0, 1]])
Stack arrays
Selection
Single item
Slicing
arr = np.arange(10)
arr[5:8] # returns [5, 6, 7]
arr[5:8] = 12 # all three values are overwritten (would give error on a
list)
arr[arr > 5]
Boolean selection return a view which authorizes the modification of the original array
arr[arr > 5] = 0
print(arr)
Vectorized operations
nums = np.arange(5)
nums * 10 # multiply each element by 10
nums = np.sqrt(nums) # square root of each element
np.ceil(nums) # also floor, rint (round to nearest int)
np.isnan(nums) # checks for NaN
nums + np.arange(5) # add element-wise
np.maximum(nums, np.array([1, -2, 3, -4, 5])) # compare element-wise
# random numbers
np.random.seed(12234) # Set the seed
Broadcasting
Sources https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html
Implicit conversion to allow operations on arrays of different sizes.
The smaller array is stretched or broadcasted across the larger array so that they have
compatible shapes.
Fast vectorized operation in C instead of Python.
No needless copies.
Rules
Starting with the trailing axis and working backward, Numpy compares arrays dimensions.
If two dimensions are equal then continues
If one of the operand has dimension 1 stretches it to match the largest one
When one of the shapes runs out of dimensions (because it has less dimensions than the other shape), Numpy
will use 1 in the comparison process until the other shapes dimensions run out as well.
a = np.array([[ 0, 0, 0],
[10, 10, 10],
[20, 20, 20],
[30, 30, 30]])
b = np.array([0, 1, 2])
a + b
array([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]])
Examples
Shapes of operands A, B and result:
A (2d array): 5 x 4
B (1d array): 1
Result (2d array): 5 x 4
A (2d array): 5 x 4
B (1d array): 4
Result (2d array): 5 x 4
A (3d array): 15 x 3 x 5
B (3d array): 15 x 1 x 5
Result (3d array): 15 x 3 x 5
3.6. Broadcasting 25
Statistics and Machine Learning in Python, Release 0.1
A (3d array): 15 x 3 x 5
B (2d array): 3 x 5
Result (3d array): 15 x 3 x 5
A (3d array): 15 x 3 x 5
B (2d array): 3 x 1
Result (3d array): 15 x 3 x 5
Exercises
For each column find the row index of the minimum value.
Write a function standardize(X) that return an array whose columns are centered and scaled (by std-dev).
FOUR
It is often said that 80% of data analysis is spent on the cleaning and preparing data. To get a handle on the problem,
this chapter focuses on a small, but important, aspect of data manipulation and cleaning with Pandas.
Sources:
Kevin Markham: https://github.com/justmarkham
Pandas doc: http://pandas.pydata.org/pandas-docs/stable/index.html
Data structures
Series is a one-dimensional labeled array capable of holding any data type (integers, strings, floating point
numbers, Python objects, etc.). The axis labels are collectively referred to as the index. The basic method to
create a Series is to call pd.Series([1,3,5,np.nan,6,8])
DataFrame is a 2-dimensional labeled data structure with columns of potentially different types. You can think
of it like a spreadsheet or SQL table, or a dict of Series objects. It stems from the R data.frame() object.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Create DataFrame
27
Statistics and Machine Learning in Python, Release 0.1
Concatenate DataFrame
user1.append(user2)
users = pd.concat([user1, user2, user3])
print(users)
Join DataFrame
# height name
#0 165 alice
#1 180 john
#2 175 eric
#3 171 julie
print(merge_inter)
Summarizing
Columns selection
4.4. Summarizing 29
Statistics and Machine Learning in Python, Release 0.1
Rows selection
for i in range(users.shape[0]):
row = df.iloc[i]
row.age *= 100 # setting a copy, and not the original frame data.
for i in range(df.shape[0]):
df.ix[i, "age"] *= 10
print(df) # df is modified
Sorting
df = users.copy()
Reshaping by pivoting
df = users.append(df.iloc[0], ignore_index=True)
Rename values
df = users.copy()
print(df.columns)
df.columns = ['age', 'genre', 'travail', 'nom', 'taille']
Assume random variable follows the normal distribution Exclude data outside 3 standard-deviations: - Probability
that a sample lies within 1 sd: 68.27% - Probability that a sample lies within 3 sd: 99.73% (68.27 + 2 * 15.73)
https://fr.wikipedia.org/wiki/Loi_normale#/media/File:Boxplot_vs_PDF.svg
size_outlr_mean = size.copy()
size_outlr_mean[((size - size.mean()).abs() > 3 * size.std())] = size.mean()
print(size_outlr_mean.mean())
Median absolute deviation (MAD), based on the median, is a robust non-parametric statistics. https://en.wikipedia.
org/wiki/Median_absolute_deviation
mad = 1.4826 * np.median(np.abs(size - size.median()))
size_outlr_mad = size.copy()
Groupby
File I/O
csv
url = 'https://raw.github.com/neurospin/pystatsml/master/data/salary_table.csv'
salary = pd.read_csv(url)
Excel
pd.read_excel(xls_filename, sheetname='users')
# Multiple sheets
with pd.ExcelWriter(xls_filename) as writer:
users.to_excel(writer, sheet_name='users', index=False)
df.to_excel(writer, sheet_name='salary', index=False)
4.14. Groupby 33
Statistics and Machine Learning in Python, Release 0.1
pd.read_excel(xls_filename, sheetname='users')
pd.read_excel(xls_filename, sheetname='salary')
Exercises
Data Frame
Missing data
df = users.copy()
df.ix[[0, 2], "age"] = None
df.ix[[1, 3], "gender"] = None
1. Write a function fillmissing_with_mean(df) that fill all missing value of numerical column with the
mean of the current columns.
2. Save the original users and imputed frame in a single excel file users.xlsx with 2 sheets: original, imputed.
FIVE
Basic plots
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(x, sinus)
plt.show()
35
Statistics and Machine Learning in Python, Release 0.1
# Rapid multiplot
cosinus = np.cos(x)
plt.plot(x, sinus, "-b", x, sinus, "ob", x, cosinus, "-r", x, cosinus, "or")
plt.xlabel('this is x!')
plt.ylabel('this is y!')
plt.title('My First Plot')
plt.show()
# Step by step
plt.plot(x, sinus, label='sinus', color='blue', linestyle='--', linewidth=2)
plt.plot(x, cosinus, label='cosinus', color='red', linestyle='-', linewidth=2)
plt.legend()
plt.show()
Load dataset
import pandas as pd
try:
salary = pd.read_csv("../data/salary_table.csv")
except:
url = 'https://raw.github.com/duchesnay/pylearn-doc/master/data/salary_table.csv'
salary = pd.read_csv(url)
df = salary
<matplotlib.collections.PathCollection at 0x7f78c2ab25f8>
## Figure size
plt.figure(figsize=(6,5))
s=150, label=manager+"/"+edu)
## Set labels
plt.xlabel('Experience')
plt.ylabel('Salary')
plt.legend(loc=4) # lower right
plt.show()
Saving Figures
# Prefer vectorial format (SVG: Scalable Vector Graphics) can be edited with
# Inkscape, Adobe Illustrator, Blender, etc.
plt.plot(x, sinus)
plt.savefig("sinus.svg")
plt.close()
# Or pdf
plt.plot(x, sinus)
plt.savefig("sinus.pdf")
plt.close()
Sources: http://stanford.edu/~mwaskom/software/seaborn
Install using: pip install -U --user seaborn
Boxplot
Box plots are non-parametric: they display variation in samples of a statistical population without making any assump-
tions of the underlying statistical distribution.
<matplotlib.axes._subplots.AxesSubplot at 0x7f78c2ab44a8>
<matplotlib.axes._subplots.AxesSubplot at 0x7f78c27d9358>
One figure can contain several axis, whose contain the graphic elements
i = 0
for edu, d in salary.groupby(['education']):
sns.distplot(d.salary[d.management == "Y"], color="b", bins=10, label="Manager",
ax=axes[i])
axes[i].set_title(edu)
axes[i].set_ylabel('Density')
i += 1
plt.legend()
/usr/local/anaconda3/lib/python3.5/site-packages/statsmodels/nonparametric/kdetools.
py:20: VisibleDeprecationWarning: using a non-integer number instead of an integer
y = X[:m/2+1] + np.r_[0,X[m/2+1:],0]*1j
<matplotlib.legend.Legend at 0x7f78c32ca9b0>
g = sns.PairGrid(salary, hue="management")
g.map_diag(plt.hist)
g.map_offdiag(plt.scatter)
g.add_legend()
<seaborn.axisgrid.PairGrid at 0x7f78c2994dd8>
SIX
UNIVARIATE STATISTICS
Mean
Variance
() = (( ())2 ) = ( 2 ) (())2
The estimator is
1
2 = )2
(
1
Note here the subtracted 1 degree of freedom (df) in the divisor. In standard statistical practice, = 1 provides an
unbiased estimator of the variance of a hypothetical infinite population. With = 0 it instead provides a maximum
likelihood estimate of the variance for normally distributed variables.
45
Statistics and Machine Learning in Python, Release 0.1
Standard deviation
() = ()
The estimator is simply = 2 .
Covariance
Cov(, ) = Var()
Cov(, ) = Cov(, )
Cov(, ) = Cov(, )
Cov( + , ) = Cov(, )
Correlation
(, )
(, ) =
()( )
The estimator is
= .
The standard error (SE) is the standard deviation (of the sampling distribution) of a statistic:
()
() = .
It is most commonly considered for the mean with the estimator (
) = / .
Exercises
Generate 2 random samples: (1.78, 0.1) and (1.66, 0.1), both of size 10.
Compute , , (xbar,xvar,xycov) using only the np.sum() operation. Explore the np. module to
find out which numpy functions performs the same computations and compare them (using assert) with your
previous results.
Main distributions
Normal distribution
The normal distribution, noted (, ) with parameters: mean (location) and > 0 std-dev. Estimators:
and .
The chi-square or 2 distribution with degrees of freedom (df) is the distribution of a sum of the squares of
independent standard normal random variables (0, 1). Let (, 2 ), then, = ( )/ (0, 1), then:
The squared standard 2 21 (one df).
The distribution of sum of squares of normal random variables: 2 2
The sum of two 2 RV with and df is a 2 RV with + df. This is useful when summing/subtracting sum of
squares.
The 2 -distribution is used to model errors measured as sum of squares or the distribution of the sample variance.
The -distribution, , , with and degrees of freedom is the ratio of two independent 2 variables. Let 2
and 2 then:
/
, =
/
The -distribution plays a central role in hypothesis testing answering the question: Are two variances equals?, is
the ratio or two errors significantly large ?.
import numpy as np
from scipy.stats import f
import matplotlib.pyplot as plt
%matplotlib inline
Let (0, 1) and 2 . The -distribution, , with degrees of freedom is the ratio:
=
/
The distribution of the difference between an estimated parameter and its true (or assumed) value divided by the
standard deviation of the estimated parameter (standard error) follow a -distribution. Is this parameters different
from a given value?
A continuous or quantitative variable R is one that can take any value in a range of possible values,
possibly infinite. E.g.: salary, experience in years, weight.
What statistical test should I use? See: http://www.ats.ucla.edu/stat/mult_pkg/whatstat/
Test the correlation coefficient of two quantitative variables. The test calculates a Pearson correlation coefficient and
the -value for testing non-correlation.
import numpy as np
import scipy.stats as stats
n = 50
x = np.random.normal(size=n)
y = 2 * x + np.random.normal(size=n)
The one-sample -test is used to determine whether a sample comes from a population with a specific mean. For
example you want to test if the average height of a population is 1.75 .
3. Test
In testing the null hypothesis that the population mean is equal to a specified value 0 = 1.75, one uses the statistic:
0
=
/
Although the parent population does not need to be normally distributed, the distribution of the population of sample
means, , is assumed to be normal. By the central limit theorem, if the sampling of the parent population is independent
then the sample means will be approximately normal.
Exercise
Given the following samples, we will test whether its true mean is 1.75.
Warning, when computing the std or the variance, set ddof=1. The default value, ddof=0, leads to the biased
estimator of the variance.
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
np.random.seed(seed=42) # make example reproducible
n = 100
x = np.random.normal(loc=1.78, scale=.1, size=n) # the sample is here
_ = plt.legend()
The two-sample -test (Snedecor and Cochran, 1989) is used to determine if two population means are equal. There
are several variations on this test. If data are paired (e.g. 2 measures, before and after treatment for each individual)
use the one-sample -test of the difference. The variances of the two samples may be assumed to be equal (a.k.a.
homoscedasticity) or unequal (a.k.a. heteroscedasticity).
3. -test
Generally -tests form the ratio between the amount of information explained by the model (i.e. the effect size) with
the square root of the unexplained variance.
In testing the null hypothesis that the two population means are equal, one uses the -statistic of unpaired two samples
-test:
effect size
=
unexplained variance
This test is used only when it can be assumed that the two distributions have the same variance. The statistic, that is
used to test whether the means are different is:
= ,
1 + 1
where
2 ( 1) + 2 ( 1)
=
+ 2
is an estimator of the common standard deviation of the two samples: it is defined in this way so that its square is an
unbiased estimator of the common variance whether or not the population means are the same.
To compute the -value one needs the degrees of freedom associated with this variance estimate. It is approximated
using the WelchSatterthwaite equation:
)2
2 2
(
+
4 4
.
2 ( 1) + 2 ( 1)
Exercise
Given the following two samples, test whether their means are equal using the standard t-test, assuming equal
variance.
Analysis of variance (ANOVA) provides a statistical test of whether or not the means of several groups are equal, and
therefore generalizes the -test to more than two groups. ANOVAs are useful for comparing (testing) three or more
means (groups or variables) for statistical significance. It is conceptually similar to multiple two-sample -tests, but is
less conservative.
Here we will consider one-way ANOVA with one independent variable, ie one-way anova.
A company has applied three marketing strategies to three samples of customers in order increase their business
volume. The marketing is asking whether the strategies led to different increases of business volume. Let 1 , 2 and
3 be the three samples of business volume increase.
Here we assume that the three populations were sampled from three random variables that are normally distributed.
I.e., 1 (1 , 1 ), 2 (2 , 2 ) and 3 (3 , 3 ).
3. -test
Source: https://en.wikipedia.org/wiki/F-test
The ANOVA -test can be used to assess whether any of the strategies is on average superior, or inferior, to the others
versus the null hypothesis that all four strategies yield the same mean response (increase of business volume). This is
an example of an omnibus test, meaning that a single test is performed to detect any of several possible differences.
Alternatively, we could carry out pair-wise tests among the strategies. The advantage of the ANOVA -test is that
we do not need to pre-specify which strategies are to be compared, and we do not need to adjust for making multiple
comparisons. The disadvantage of the ANOVA -test is that if we reject the null hypothesis, we do not know which
strategies can be said to be significantly different from the others.
The formula for the one-way ANOVA F-test statistic is
explained variance
= ,
unexplained variance
or
between-group variability
= .
within-group variability
The explained variance, or between-group variability is
( )2 /( 1),
where denotes the sample mean in the th group, is the number of observations in the th group, denotes the
overall mean of the data, and denotes the number of groups.
The unexplained variance, or within-group variability is
( )2 /( ),
where is the th observation in the th out of groups and is the overall sample size. This -statistic follows
the -distribution with 1 and degrees of freedom under the null hypothesis. The statistic will be large
if the between-group variability is large relative to the within-group variability, which is unlikely to happen if the
population means of the groups all have the same value.
Note that when there are only two groups for the one-way ANOVA F-test, = 2 where is the Students statistic.
Exercise
# dataset
mu_k = np.array([1, 2, 3]) # means of 3 samples
sd_k = np.array([1, 1, 1]) # sd of 3 samples
n_k = np.array([10, 20, 30]) # sizes of 3 samples
grp = [0, 1, 2] # group labels
n = np.sum(n_k)
label = np.hstack([[k] * n_k[k] for k in [0, 1, 2]])
y = np.zeros(n)
for k in grp:
y[label == k] = np.random.normal(mu_k[k], sd_k[k], n_k[k])
Computes the chi-square, 2 , statistic and -value for the hypothesis test of independence of frequencies in the ob-
served contingency table (cross-table). The observed frequencies are tested against an expected contingency table
obtained by computing expected frequencies based on the marginal sums under the assumption of independence.
Example: 15 patients with cancer, two observed categorial variables: canalar tumor (Y/N) and metastasis (Y/N). 2
tests the association between those two variables.
import numpy as np
import pandas as pd
import scipy.stats as stats
# Dataset:
# 15 samples:
# 10 first with canalar tumor, 5 last without
canalar_tumor = np.array([1] * 10 + [0] * 5)
# 8 first with metastasis, 6 without, the last with.
meta = np.array([1] * 8 + [0] * 6 + [1])
print("Observed table:")
print("---------------")
print(crosstab)
print("---------------")
print(expected)
Observed table:
---------------
meta 0 1
canalar_tumor
0 4 1
1 2 8
Statistics:
-----------
Chi2 = 2.812500, pval = 0.093533
Expected table:
---------------
[[ 2. 3.]
[ 4. 6.]]
canalar_tumor_marg = crosstab.sum(axis=1)
canalar_tumor_freq = canalar_tumor_marg / canalar_tumor_marg.sum()
print('Expected frequencies:')
print(np.outer(canalar_tumor_freq, meta_freq))
The Spearman correlation is a non-parametric measure of the monotonicity of the relationship between two datasets.
When to use it? Observe the data distribution: - presence of outliers - the distribution of the residuals is not Gaussian.
Like other correlation coefficients, this one varies between -1 and +1 with 0 implying no correlation. Correlations of
-1 or +1 imply an exact monotonic relationship. Positive correlations imply that as increases, so does . Negative
import numpy as np
import scipy.stats as stats
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
n = 50
noutliers = 10
x = np.random.normal(size=n)
y = 2 * x + np.random.normal(size=n)
y[:noutliers] = np.random.normal(loc=-10, size=noutliers) # Add 40 outliers
outlier = np.array(["N"] * n)
outlier[:noutliers] = "Y"
Source: https://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test
The Wilcoxon signed-rank test is a non-parametric statistical hypothesis test used when comparing two related sam-
ples, matched samples, or repeated measurements on a single sample to assess whether their population mean ranks
differ (i.e. it is a paired difference test). It is equivalent to one-sample test of the difference of paired samples.
It can be used as an alternative to the paired Students -test, -test for matched pairs, or the -test for dependent
samples when the population cannot be assumed to be normally distributed.
When to use it? Observe the data distribution: - presence of outliers - the distribution of the residuals is not Gaussian
It has a lower sensitivity compared to -test. May be problematic to use when the sample size is small.
Null hypothesis 0 : difference between the pairs follows a symmetric distribution around zero.
# create an outlier
bv1[0] -= 10
# Paired t-test
print(stats.ttest_rel(bv0, bv1))
# Wilcoxon
print(stats.wilcoxon(bv0, bv1))
Ttest_relResult(statistic=0.82290246738044537, pvalue=0.42077212061718194)
WilcoxonResult(statistic=43.0, pvalue=0.020633435105949553)
In statistics, the MannWhitney test (also called the MannWhitneyWilcoxon, Wilcoxon rank-sum test or
WilcoxonMannWhitney test) is a nonparametric test of the null hypothesis that two samples come from the same
population against an alternative hypothesis, especially that a particular population tends to have larger values than the
other.
It can be applied on unknown distributions contrary to e.g. a -test that has to be applied only on normal distributions,
and it is nearly as efficient as the -test on normal distributions.
import scipy.stats as stats
n = 20
# Buismess Volume group 0
bv0 = np.random.normal(loc=1, scale=.1, size=n)
# create an outlier
bv1[0] -= 10
# Two-samples t-test
print(stats.ttest_ind(bv0, bv1))
# Wilcoxon
print(stats.mannwhitneyu(bv0, bv1))
Ttest_indResult(statistic=0.62748520384004158, pvalue=0.53409388734462837)
MannwhitneyuResult(statistic=43.0, pvalue=1.1512354940556314e-05)
Linear model
Given random samples ( , 1 , . . . , ), = 1, . . . , , the linear regression models the relation between the obser-
vations and the independent variables is formulated as
= 0 + 1 1 + + + = 1, . . . ,
An independent variable (IV). It is a variable that stands alone and isnt changed by the other variables you
are trying to measure. For example, someones age might be an independent variable. Other factors (such as
what they eat, how much they go to school, how much television they watch) arent going to change a persons
age. In fact, when you are looking for some kind of relationship between variables you are trying to see if the
independent variable causes some kind of change in the other variables, or dependent variables. In Machine
Learning, these variables are also called the predictors.
A dependent variable. It is something that depends on other factors. For example, a test score could be a
dependent variable because it could change depending on several factors such as how much you studied, how
much sleep you got the night before you took the test, or even how hungry you were when you took it. Usually
when you are looking for a relationship between two things you are trying to find out what makes the dependent
variable change the way it does. In Machine Learning this variable is called a target variable.
Using the dataset salary, explore the association between the dependant variable (e.g. Salary) and the independent
variable (e.g.: Experience is quantitative).
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
url = 'https://raw.github.com/neurospin/pystatsml/master/data/salary_table.csv'
salary = pd.read_csv(url)
Model the data on some hypothesis e.g.: salary is a linear function of the experience.
salary = experience + 0 + ,
more generally
= + 0 +
Recall from calculus that an extreme point can be found by computing where the derivative is zero, i.e. to find the
intercept, we perform the steps:
= ( 0 ) = 0
0
= + 0
=
+ 0
0 =
= ( 0 ) = 0
Plug in 0 :
( +
) = 0
= (
)
3. -Test
The goodness of fit of a statistical model describes how well it fits a set of observations. Measures of goodness of fit
typically summarize the discrepancy between observed values and the values expected under the model in question.
We will consider the explained variance also known as the coefficient of determination, denoted 2 pronounced
R-squared.
The total sum of squares, tot is the sum of the sum of squares explained by the regression, reg , plus the sum of
squares of residuals unexplained by the regression, res , also called the SSE, i.e. such that
The mean of is
1
= .
The total sum of squares is the total squared sum of deviations from the mean of , i.e.
tot = ( )2
The regression sum of squares, also called the explained sum of squares:
reg = )2 ,
(
The sum of squares of the residuals, also called the residual sum of squares (RSS) is:
res = ( )2 .
2
is the explained sum of squares of errors. It is the variance explain by the regression divided by the total variance,
i.e.
explained SS reg
2 = = =1 .
total SS
3.2 Test
2 = res /( 2) be an estimator of the variance of . The 2 in the denominator stems from the 2 estimated
Let
parameters: intercept and coefficient.
res
Unexplained variance: ^2
22
tot
Explained variance: ^ 2reg 21 . The single degree of freedom comes from the difference between ^ 2 (
21 ) and 2
^ 2 ( 2 ), i.e. ( 1) ( 2) degree of freedom.
res
Multiple regression
Theory
( , ) = 0 + 1 1 + ... +
,
or, simplified
1
( , ) = 0 + .
=1
Extending each sample with an intercept, := [1, ] +1 allows us to use a more general notation based on
linear algebra and write it as a simple dot product:
( , ) = ,
where +1 is a vector of weights that define the +1 parameters of the model. From now we have regressors
+ the intercept.
Minimize the Mean Squared Error MSE loss:
1 1
() = ( ( , ))2 = ( )2
=1 =1
Let = [0 , ..., ] be a + 1 matrix of samples of input features with one column of one and let be
= [1 , ..., ] be a vector of the targets. Then, using linear algebra, the mean squared error (MSE) loss can be
rewritten:
1
() = || ||22 .
The that minimises the MSE can be found by:
( )
1
|| ||22 =0 (6.4)
1
( ) ( ) = 0 (6.5)
1
( 2 + ) = 0 (6.6)
2 + 2 = 0 (6.7)
= (6.8)
1
= ( ) , (6.9)
import numpy as np
import scipy
np.random.seed(seed=42) # make the example reproducible
# Dataset
N, P = 50, 4
X = np.random.normal(size= N * P).reshape((N, P))
## Our model needs an intercept so we add a column of 1s:
X[:, 0] = 1
print(X[:5, :])
y = np.dot(X, betastar) + e
Sources: http://statsmodels.sourceforge.net/devel/examples/
Multiple regression
import statsmodels.api as sm
==============================================================================
Omnibus: 2.493 Durbin-Watson: 2.369
Prob(Omnibus): 0.288 Jarque-Bera (JB): 1.544
Skew: 0.330 Prob(JB): 0.462
Kurtosis: 3.554 Cond. No. 1.27
==============================================================================
Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly
specified.
Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly
specified.
Analysis of covariance (ANCOVA) is a linear model that blends ANOVA and linear regression. ANCOVA evaluates
whether population means of a dependent variable (DV) are equal across levels of a categorical independent variable
(IV) often called a treatment, while statistically controlling for the effects of other quantitative or continuous variables
that are not of primary interest, known as covariates (CV).
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
try:
salary = pd.read_csv("../data/salary_table.csv")
except:
url = 'https://raw.github.com/neurospin/pystatsml/master/data/salary_table.csv'
salary = pd.read_csv(url)
One-way AN(C)OVA
Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly
specified.
sum_sq df F PR(>F)
management 5.755739e+08 1.0 183.593466 4.054116e-17
experience 3.334992e+08 1.0 106.377768 3.349662e-13
Residual 1.348070e+08 43.0 NaN NaN
Two-way AN(C)OVA
--------------------------------------------------------------------------------------
-
==============================================================================
Omnibus: 2.293 Durbin-Watson: 2.237
Prob(Omnibus): 0.318 Jarque-Bera (JB): 1.362
Skew: -0.077 Prob(JB): 0.506
Kurtosis: 2.171 Cond. No. 33.5
==============================================================================
Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly
specified.
sum_sq df F PR(>F)
education 9.152624e+07 2.0 43.351589 7.672450e-11
oneway is nested within twoway. Comparing two nested models tells us if the additional predictors (i.e.
education) of the full model significantly decrease the residuals. Such comparison can be done using an -test on
residuals:
Factor coding
See http://statsmodels.sourceforge.net/devel/contrasts.html
By default Pandas use dummy coding. Explore:
print(twoway.model.data.param_names)
print(twoway.model.data.exog[:10, :])
[[ 1. 0. 0. 1. 1.]
[ 1. 0. 1. 0. 1.]
[ 1. 0. 1. 1. 1.]
[ 1. 1. 0. 0. 1.]
[ 1. 0. 1. 0. 1.]
[ 1. 1. 0. 1. 2.]
[ 1. 1. 0. 0. 2.]
[ 1. 0. 0. 0. 2.]
[ 1. 0. 1. 0. 2.]
[ 1. 1. 0. 0. 3.]]
Multiple comparisons
import numpy as np
np.random.seed(seed=42) # make example reproducible
# Dataset
n_samples, n_features = 100, 1000
n_info = int(n_features/10) # number of features with information
n1, n2 = int(n_samples/2), n_samples - int(n_samples/2)
snr = .5
Y = np.random.randn(n_samples, n_features)
grp = np.array(["g1"] * n1 + ["g2"] * n2)
#
import scipy.stats as stats
import matplotlib.pyplot as plt
tvals, pvals = np.full(n_features, np.NAN), np.full(n_features, np.NAN)
for j in range(n_features):
tvals[j], pvals[j] = stats.ttest_ind(Y[grp=="g1", j], Y[grp=="g2", j],
equal_var=True)
axis[2].hist([pvals[n_info:], pvals[:n_info]],
stacked=True, bins=100, label=["Negatives", "Positives"])
axis[2].set_xlabel("p-value histogram")
axis[2].set_ylabel("density")
axis[2].legend()
plt.tight_layout()
Note that under the null hypothesis the distribution of the p-values is uniform.
Statistical measures:
True Positive (TP) equivalent to a hit. The test correctly concludes the presence of an effect.
True Negative (TN). The test correctly concludes the absence of an effect.
False Positive (FP) equivalent to a false alarm, Type I error. The test improperly concludes the presence of an
effect. Thresholding at -value < 0.05 leads to 47 FP.
False Negative (FN) equivalent to a miss, Type II error. The test improperly concludes the absence of an effect.
The Bonferroni correction is based on the idea that if an experimenter is testing hypotheses, then one way of
maintaining the familywise error rate (FWER) is to test each individual hypothesis at a statistical significance level of
1/ times the desired maximum overall level.
So, if the desired significance level for the whole family of tests is (usually 0.05), then the Bonferroni correction
would test each individual hypothesis at a significance level of / . For example, if a trial is testing = 8 hypotheses
with a desired = 0.05, then the Bonferroni correction would test each individual hypothesis at = 0.05/8 =
0.00625.
FDR-controlling procedures are designed to control the expected proportion of rejected null hypotheses that were
incorrect rejections (false discoveries). FDR-controlling procedures provide less stringent control of Type I errors
compared to the familywise error rate (FWER) controlling procedures (such as the Bonferroni correction), which
control the probability of at least one Type I error. Thus, FDR-controlling procedures have greater power, at the cost
of increased rates of Type I errors.
Exercise
Write a function univar_stat(df,target,variables) that computes the parametric statistics and -values
between the target variable (provided as as string) and all variables (provided as a list of string) of the pandas
DataFrame df. The target is a quantitative variable but variables may be quantitative or qualitative. The function
returns a DataFrame with four columns: variable, test, value, p_value.
Apply it to the salary dataset available at https://raw.github.com/neurospin/pystatsml/master/data/salary_table.csv,
with target being S: salaries for IT staff in a corporation.
6.8. Exercise 71
Statistics and Machine Learning in Python, Release 0.1
Multiple regression
Considering the simulated data used to fit the linear regression with numpy:
1. What are the dimensions of pinv()?
2. Compute the MSE between the predicted values and the true values.
Multiple comparisons
This exercise has 2 goals: apply you knowledge of statistics using vectorized numpy operations. Given the dataset
provided for multiple comparisons, compute the two-sample -test (assuming equal variance) for each (column) feature
of the Y array given the two groups defined by grp variable. You should return two vectors of size n_features:
one for the -values and one for the -values.
SEVEN
MULTIVARIATE STATISTICS
Multivariate statistics includes all statistical techniques for analyzing samples made of two or more variables. The
data set (a matrix X) is a collection of independent samples column vectors [x1 , . . . , x , . . . , x ] of length
x1 11 1 1 11 . . . 1
.. .. .. .. .. ..
. . . .
. .
X = x = 1 =
X
.
. . . . . .
. . . . . .
. . . . . .
x 1 1 . . .
Linear Algebra
Source: Wikipedia
Algebraic definition
The dot product, denoted of two -dimensional vectors a = [1 , 2 , ..., ] and a = [1 , 2 , ..., ] is defined as
1
..
] .
a b = a b = = 1 . . . a . . .
[
b .
.
..
73
Statistics and Machine Learning in Python, Release 0.1
The Euclidean norm of a vector can be computed using the dot product, as
a2 = a a.
a b = a2 b2 cos ,
a b = 0.
At the other extreme, if they are codirectional, then the angle between them is 0 and
a b = a2 b2
The scalar projection (or scalar component) of a Euclidean vector a in the direction of a Euclidean vector b is given
by
= a2 cos ,
import numpy as np
np.random.seed(42)
a = np.random.randn(10)
b = np.random.randn(10)
np.dot(a, b)
-4.0857885326599241
Mean vector
Covariance matrix
The covariance matrix XX is a symmetric positive semi-definite matrix whose element in the , position is
the covariance between the and elements of a random vector i.e. the and columns of X.
The covariance matrix generalizes the notion of covariance to multiple dimensions.
The covariance matrix describe the shape of the sample distribution around the mean assuming an elliptical
distribution:
XX = (X (X)) (X (X)),
whose estimator SXX is a matrix given by
1
SXX = (X 1x ) (X 1x ).
1
where
1 1
= = xj xk =
1 1 =1
np.random.seed(42)
colors = sns.color_palette()
# Generate dataset
for i in range(len(mean)):
X[i] = np.random.multivariate_normal(mean[i], Cov[i], n_samples)
# Plot
for i in range(len(mean)):
# Points
plt.scatter(X[i][:, 0], X[i][:, 1], color=colors[i], label="class %i" % i)
# Means
plt.scatter(mean[i][0], mean[i][1], marker="o", s=200, facecolors='w',
edgecolors=colors[i], linewidth=2)
# Ellipses representing the covariance matrices
pystatsml.plot_utils.plot_cov_ellipse(Cov[i], pos=mean[i], facecolor='none',
linewidth=2, edgecolor=colors[i])
plt.axis('equal')
_ = plt.legend(loc='upper left')
Precision matrix
In statistics, precision is the reciprocal of the variance, and the precision matrix is the matrix inverse of the covariance
matrix.
It is related to partial correlations that measures the degree of association between two variables, while controlling
the effect of other variables.
import numpy as np
print(Pcor.round(2))
# Precision matrix:
[[ 6.79 -3.21 -3.21 0. 0. 0. ]
[-3.21 6.79 -3.21 0. 0. 0. ]
[-3.21 -3.21 6.79 0. 0. 0. ]
[ 0. 0. 0. 5.26 -4.74 0. ]
[ 0. 0. 0. -4.74 5.26 0. ]
[ 0. 0. 0. 0. 0. 1. ]]
# Partial correlations:
[[ nan 0.47 0.47 -0. -0. -0. ]
[ nan nan 0.47 -0. -0. -0. ]
[ nan nan nan -0. -0. -0. ]
[ nan nan nan nan 0.9 -0. ]
[ nan nan nan nan nan -0. ]
[ nan nan nan nan nan nan]]
Mahalanobis distance
The Mahalanobis distance is a measure of the distance between two points x and where the dispersion (i.e.
the covariance structure) of the samples is taken into account.
The dispersion is considered through covariance matrix.
This is formally expressed as
(x, ) = (x ) 1 (x ).
Intuitions
Distances along the principal directions of dispersion are contracted since they correspond to likely dispersion
of points.
Distances othogonal to the principal directions of dispersion are dilated since they correspond to unlikely dis-
persion of points.
For example
(1) = 1 1 1.
ones = np.ones(Cov.shape[0])
d_euc = np.sqrt(np.dot(ones, ones))
d_mah = np.sqrt(np.dot(np.dot(ones, Prec), ones))
The first dot product that distances along the principal directions of dispersion are contracted:
print(np.dot(ones, Prec))
import numpy as np
import scipy
import matplotlib.pyplot as plt
import seaborn as sns
import pystatsml.plot_utils
%matplotlib inline
np.random.seed(40)
colors = sns.color_palette()
Covi = scipy.linalg.inv(Cov)
dm_m_x1 = scipy.spatial.distance.mahalanobis(mean, x1, Covi)
dm_m_x2 = scipy.spatial.distance.mahalanobis(mean, x2, Covi)
# Plot distances
vm_x1 = (x1 - mean) / d2_m_x1
vm_x2 = (x2 - mean) / d2_m_x2
jitter = .1
plt.plot([mean[0] - jitter, d2_m_x1 * vm_x1[0] - jitter],
[mean[1], d2_m_x1 * vm_x1[1]], color='k')
plt.plot([mean[0] - jitter, d2_m_x2 * vm_x2[0] - jitter],
[mean[1], d2_m_x2 * vm_x2[1]], color='k')
plt.legend(loc='lower right')
plt.text(-6.1, 3,
'Euclidian: d(m, x1) = %.1f<d(m, x2) = %.1f' % (d2_m_x1, d2_m_x2), color='k
')
plt.text(-6.1, 3.5,
'Mahalanobis: d(m, x1) = %.1f>d(m, x2) = %.1f' % (dm_m_x1, dm_m_x2), color='r
')
plt.axis('equal')
print('Euclidian d(m, x1) = %.2f < d(m, x2) = %.2f' % (d2_m_x1, d2_m_x2))
print('Mahalanobis d(m, x1) = %.2f > d(m, x2) = %.2f' % (dm_m_x1, dm_m_x2))
If the covariance matrix is the identity matrix, the Mahalanobis distance reduces to the Euclidean distance. If the
covariance matrix is diagonal, then the resulting distance measure is called a normalized Euclidean distance.
More generally, the Mahalanobis distance is a measure of the distance between a point x and a distribution (x|, ).
It is a multi-dimensional generalization of the idea of measuring how many standard deviations away x is from the
mean. This distance is zero if x is at the mean, and grows as x moves away from the mean: along each principal
component axis, it measures the number of standard deviations from x to the mean of the distribution.
The distribution, or probability density function (PDF) (sometimes just density), of a continuous random variable is a
function that describes the relative likelihood for this random variable to take on a given value.
The multivariate normal distribution, or multivariate Gaussian distribution, of a -dimensional random vector x =
[1 , 2 , . . . , ] is
1 1
(x|, ) = exp{ (x ) 1 (x )}.
(2)/2 ||1/2 2
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats
from scipy.stats import multivariate_normal
from mpl_toolkits.mplot3d import Axes3D
P = X.shape[1]
det = np.linalg.det(sigma)
norm_const = 1.0 / (((2*np.pi) ** (P/2)) * np.sqrt(det))
X_mu = X - mu
inv = np.linalg.inv(sigma)
d2 = np.sum(np.dot(X_mu, inv) * X_mu, axis=1)
return norm_const * np.exp(-0.5 * d2)
# x, y grid
x, y = np.mgrid[-3:3:.1, -3:3:.1]
X = np.stack((x.ravel(), y.ravel())).T
norm = multivariate_normal_pdf(X, mean, sigma).reshape(x.shape)
# Do it with scipy
norm_scpy = multivariate_normal(mu, sigma).pdf(np.stack((x, y), axis=2))
assert np.allclose(norm, norm_scpy)
# Plot
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x, y, norm, rstride=3,
cstride=3, cmap=plt.cm.coolwarm,
linewidth=1, antialiased=False
)
ax.set_zlim(0, 0.2)
ax.zaxis.set_major_locator(plt.LinearLocator(10))
ax.zaxis.set_major_formatter(plt.FormatStrFormatter('%.02f'))
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('p(x)')
Exercises
7.7. Exercises 81
Statistics and Machine Learning in Python, Release 0.1
EIGHT
Introduction
In machine learning and statistics, dimensionality reduction or dimension reduction is the process of reducing the
number of features under consideration, and can be divided into feature selection (not addressed here) and feature
extraction.
Feature extraction starts from an initial set of measured data and builds derived values (features) intended to be infor-
mative and non-redundant, facilitating the subsequent learning and generalization steps, and in some cases leading to
better human interpretations. Feature extraction is related to dimensionality reduction.
The input matrix X, of dimension , is
11 ... 1
.. ..
.
X .
1 ...
where the rows represent the samples and columns represent the variables.
The goal is to learn a transformation that extracts a few relevant features. This is generally done by exploiting the
covariance XX between the input features.
Decompose the data matrix X into a product of a mixing matrix U and a dictionary matrix V .
X = UV ,
If we consider only a subset of components < (X) < min(, 1) , X is approximated by a matrix X:
X X = UV ,
83
Statistics and Machine Learning in Python, Release 0.1
X = UDV ,
where
11 1 11 1
1 0 11 1
X =
U
D V .
0 1
1 1
U: right-singular
V = [v1 , , v ] is a orthogonal matrix.
It is a dictionary of patterns to be combined (according to the mixing coefficients) to reconstruct the original
samples.
V perfoms the initial rotations (projection) along the = min(, ) principal component directions, also
called loadings.
Each v performs the linear combination of the variables that has maximum sample variance, subject to being
uncorrelated with the previous v1 .
D: singular values
D is a diagonal matrix made of the singular values of X with 1 2 0.
D scale the projection along the coordinate axes by 1 , 2 , , .
Singular values are the square roots of the eigenvalues of X X.
V: left-singular vectors
U = [u1 , , u ] is an orthogonal matrix.
Each row vi provides the mixing coefficients of dictionary items to reconstruct the sample xi
It may be understood as the coordinates on the new orthogonal basis (obtained after the initial rotation) called
principal components in the PCA.
V transforms correlated variables (X) into a set of uncorrelated ones (UD) that better expose the various relationships
among the original data items.
X = UDV , (8.1)
XV = UDV V, (8.2)
XV = UDI, (8.3)
XV = UD (8.4)
At the same time, SVD is a method for identifying and ordering the dimensions along which data points exhibit the
most variation.
import numpy as np
import scipy
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
np.random.seed(42)
# dataset
n_samples = 100
experience = np.random.normal(size=n_samples)
salary = 1500 + experience + np.random.normal(size=n_samples, scale=.5)
X = np.column_stack([experience, salary])
plt.figure(figsize=(9, 3))
plt.subplot(131)
plt.scatter(U[:, 0], U[:, 1], s=50)
plt.axis('equal')
plt.title("U: Rotated and scaled data")
plt.subplot(132)
# Project data
PC = np.dot(X, Vh.T)
plt.subplot(133)
plt.scatter(X[:, 0], X[:, 1], s=50)
for i in range(Vh.shape[0]):
plt.arrow(x=0, y=0, dx=Vh[i, 0], dy=Vh[i, 1], head_width=0.2,
head_length=0.2, linewidth=2, fc='r', ec='r')
plt.text(Vh[i, 0], Vh[i, 1],'v%i' % (i+1), color="r", fontsize=15,
horizontalalignment='right', verticalalignment='top')
plt.axis('equal')
plt.ylim(-4, 4)
plt.tight_layout()
Sources:
C. M. Bishop Pattern Recognition and Machine Learning, Springer, 2006
Everything you did and didnt know about PCA
Principal Component Analysis in 3 Simple Steps
Principles
Principal components analysis is the main method used for linear dimension reduction.
The idea of principal component analysis is to find the principal components directions (called the load-
ings) V that capture the variation in the data as much as possible.
C = X V
PCA can be thought of as fitting a -dimensional ellipsoid to the data, where each axis of the ellipsoid represents
a principal component. If some axis of the ellipse is small, then the variance along that axis is also small, and
by omitting that axis and its corresponding principal component from our representation of the dataset, we lose
only a commensurately small amount of information.
Finding the largest axes of the ellipse will permit to project the data onto a space having dimensionality
< while maximizing the variance of the projected data.
Dataset preprocessing
Centering
Consider a data matrix, X , with column-wise zero empirical mean (the sample mean of each column has been shifted
to zero), ie. X is replaced by X 1x .
Standardizing
Optionally, standardize the columns, i.e., scale them by their standard-deviation. Without standardization, a variable
with a high variance will capture most of the effect of the PCA. The principal direction will be aligned with this
variable. Standardization will, however, raise noise variables to the save level as informative variables.
The covariance matrix of centered standardized data is the correlation matrix.
To begin with, consider the projection onto a one-dimensional space ( = 1). We can define the direction of this
space using a -dimensional vector v, which for convenience (and without loss of generality) we shall choose to be a
unit vector so that v2 = 1 (note that we are only interested in the direction defined by v, not in the magnitude of v
itself). PCA consists of two mains steps:
Projection in the directions that capture the greatest variance
Each -dimensional data point x is then projected onto v, where the coordinate (in the coordinate system of v) is a
scalar value, namely x v. I.e., we want to find the vector v that maximizes these coordinates along v, which we will
see corresponds to maximizing the variance of the projected data. This is equivalently expressed as
1 ( )2
v = arg max x v .
v=1
where SXX is a biased estiamte of the covariance matrix of the data, i.e.
1
SXX = X X.
We now maximize the projected variance v SXX v with respect to v. Clearly, this has to be a constrained maximiza-
tion to prevent v2 . The appropriate constraint comes from the normalization condition v2 v22 =
v v = 1. To enforce this constraint, we introduce a Lagrange multiplier that we shall denote by , and then make an
unconstrained maximization of
v SXX v (v v 1).
By setting the gradient with respect to v equal to zero, we see that this quantity has a stationary point when
SXX v = v.
v SXX v = ,
and so the variance will be at a maximum when v is equal to the eigenvector corresponding to the largest eigenvalue,
. This eigenvector is known as the first principal component.
We can define additional principal components in an incremental fashion by choosing each new direction to be that
which maximizes the projected variance amongst all possible directions that are orthogonal to those already consid-
ered. If we consider the general case of a -dimensional projection space, the optimal linear projection for which the
variance of the projected data is maximized is now defined by the eigenvectors, v1 , . . . , vK , of the data covariance
matrix SXX that corresponds to the largest eigenvalues, 1 2 .
Back to SVD
X X = (UDV ) (UDV )
= VD U UDV
= VD2 V
V X XV = D2
1 1
V X XV = D2
1 1
1
V SXX V = D2
1
.
PCA outputs
The SVD or the eigendecomposition of the data covariance matrix provides three main quantities:
1. Principal component directions or loadings are the eigenvectors of X X. The V or the right-singular
vectors of an SVD of X are called principal component directions of X. They are generally computed using
the SVD of X.
2. Principal components is the matrix C which is obtained by projecting X onto the principal components
directions, i.e.
C = X V .
Since X = UDV and V is orthogonal (V V = I):
C = UDV V (8.5)
C = UD I (8.6)
C = UD (8.7)
(8.8)
Thus c = Xv = u , for = 1, . . . . Hence u is simply the projection of the row vectors of X, i.e., the input
predictor vectors, on the direction v , scaled by .
1,1 1,1 + . . . + 1, 1,
2,1 1,1 + . . . + 2, 1,
c1 =
..
.
,1 1,1 + . . . + , 1,
3. The variance of each component is given by the eigen values , = 1, . . . . It can be obtained from the
singular values:
1
(c ) = (Xv )2 (8.9)
1
1
= (u )2 (8.10)
1
1
= 2 (8.11)
1
We must choose * [1, . . . , ], the number of required components. This can be done by calculating the explained
variance ratio of the * first components and by choosing * such that the cumulative explained variance ratio is
PCs
Plot the samples projeted on first the principal components as e.g. PC1 against PC2.
PC directions
Exploring the loadings associated with a component provides the contribution of each original variable in the compo-
nent.
Remark: The loadings (PC directions) are the coefficients of multiple regression of PC on original variables:
c = Xv (8.12)
X c = X Xv (8.13)
1
(X X) X c=v (8.14)
Another way to evaluate the contribution of the original variables in each PC can be obtained by computing the
correlation between the PCs and the original variables, i.e. columns of X, denoted x , for = 1, . . . , . For the
PC, compute and plot the correlations with all original variables
(c , x ), = 1 . . . , = 1 . . . .
np.random.seed(42)
# dataset
n_samples = 100
experience = np.random.normal(size=n_samples)
salary = 1500 + experience + np.random.normal(size=n_samples, scale=.5)
X = np.column_stack([experience, salary])
PC = pca.transform(X)
plt.subplot(121)
plt.scatter(X[:, 0], X[:, 1])
plt.xlabel("x1"); plt.ylabel("x2")
plt.subplot(122)
plt.scatter(PC[:, 0], PC[:, 1])
plt.xlabel("PC1 (var=%.2f)" % pca.explained_variance_ratio_[0])
plt.ylabel("PC2 (var=%.2f)" % pca.explained_variance_ratio_[1])
plt.axis('equal')
plt.tight_layout()
[ 0.93646607 0.06353393]
The Sammon mapping performs better at preserving small distances compared to the least-squares scaling.
Example
The eurodist datset provides the road distances (in kilometers) between 21 cities in Europe. Given this matrix
of pairwise (non-Euclidean) distances D = [ ], MDS can be used to recover the coordinates of the cities in some
Euclidean referential whose orientation is arbitrary.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
print(df.ix[:5, :5])
city = df["city"]
D = np.array(df.ix[:, 1:]) # Distance matrix
X = mds.fit_transform(D)
for i in range(len(city)):
plt.text(Xr[i, 0], Xr[i, 1], city[i])
plt.axis('equal')
We must choose * {1, . . . , } the number of required components. Plotting the values of the stress function,
obtained using 1 components. In general, start with 1, . . . 4. Choose * where you can clearly
distinguish an elbow in the stress curve.
Thus, in the plot below, we choose to retain information accounted for by the first two components, since this is where
the elbow is in the stress curve.
print(stress)
plt.plot(k_range, stress)
plt.xlabel("k")
plt.ylabel("stress")
<matplotlib.text.Text at 0x7fefc49a2518>
Sources:
Scikit-learn documentation
Wikipedia
Nonlinear dimensionality reduction or manifold learning cover unsupervised methods that attempt to identify low-
dimensional manifolds within the original -dimensional space that represent high data density. Then those methods
provide a mapping from the high-dimensional space to the low-dimensional embedding.
Isomap
Isomap is a nonlinear dimensionality reduction method that combines a procedure to compute the distance matrix with
MDS. The distances calculation is based on geodesic distances evaluated on neighborhood graph:
1. Determine the neighbors of each point. All points in some fixed radius or K nearest neighbors.
2. Construct a neighborhood graph. Each point is connected to other if it is a K nearest neighbor. Edge length
equal to Euclidean distance.
3. Compute shortest path between pairwise of points to build the distance matrix D.
4. Apply MDS on D.
ax = fig.add_subplot(121, projection='3d')
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap=plt.cm.Spectral)
ax.view_init(4, -72)
plt.title('2D "S shape" manifold in 3D')
Y = manifold.Isomap(n_neighbors=10, n_components=2).fit_transform(X)
ax = fig.add_subplot(122)
plt.scatter(Y[:, 0], Y[:, 1], c=color, cmap=plt.cm.Spectral)
plt.title("Isomap")
plt.xlabel("First component")
plt.ylabel("Second component")
plt.axis('tight')
(-5.4131242078919239,
5.2729984345096854,
-1.2877687637642998,
1.2316524684384262)
Exercises
PCA
MDS
8.6. Exercises 97
Statistics and Machine Learning in Python, Release 0.1
NINE
CLUSTERING
Wikipedia: Cluster analysis or clustering is the task of grouping a set of objects in such a way that objects in the
same group (called a cluster) are more similar (in some sense or another) to each other than to those in other groups
(clusters). Clustering is one of the main task of exploratory data mining, and a common technique for statistical data
analysis, used in many fields, including machine learning, pattern recognition, image analysis, information retrieval,
and bioinformatics.
Sources: http://scikit-learn.org/stable/modules/clustering.html
K-means clustering
which represents the sum of the squares of the Euclidean distances of each data point to its assigned vector . Our
goal is to find values for the { } and the { } so as to minimize the function . We can do this through an iterative
procedure in which each iteration involves two successive steps corresponding to successive optimizations with respect
to the and the . First we choose some initial values for the . Then in the first phase we minimize with
respect to the , keeping the fixed. In the second phase we minimize with respect to the , keeping
fixed. This two-stage optimization process is then repeated until convergence. We shall see that these two stages of
updating and correspond respectively to the expectation (E) and maximization (M) steps of the expectation-
maximisation (EM) algorithm, and to emphasize this we shall use the terms E step and M step in the context of the
-means algorithm.
Consider first the determination of the . Because in is a linear function of , this optimization can be performed
easily to give a closed form solution. The terms involving different are independent and so we can optimize for each
99
Statistics and Machine Learning in Python, Release 0.1
separately by choosing to be 1 for whichever value of gives the minimum value of || ||2 . In other words,
we simply assign the th data point to the closest cluster centre. More formally, this can be expressed as
{
1, if = arg min || ||2 .
= (9.1)
0, otherwise.
Now consider the optimization of the with the held fixed. The objective function is a quadratic function of
, and it can be minimized by setting its derivative with respect to to zero giving
2 ( ) = 0
The denominator in this expression is equal to the number of points assigned to cluster , and so this result has a simple
interpretation, namely set equal to the mean of all of the data points assigned to cluster . For this reason, the
procedure is known as the -means algorithm.
The two phases of re-assigning data points to clusters and re-computing the cluster means are repeated in turn until
there is no further change in the assignments (or until some maximum number of iterations is exceeded). Because
each phase reduces the value of the objective function , convergence of the algorithm is assured. However, it may
converge to a local rather than global minimum of .
iris = datasets.load_iris()
X = iris.data[:, :2] # use only 'sepal length and sepal width'
y_iris = iris.target
km2 = cluster.KMeans(n_clusters=2).fit(X)
km3 = cluster.KMeans(n_clusters=3).fit(X)
km4 = cluster.KMeans(n_clusters=4).fit(X)
plt.figure(figsize=(9, 3))
plt.subplot(131)
plt.scatter(X[:, 0], X[:, 1], c=km2.labels_)
plt.title("K=2, J=%.2f" % km2.inertia_)
plt.subplot(132)
plt.scatter(X[:, 0], X[:, 1], c=km3.labels_)
plt.title("K=3, J=%.2f" % km3.inertia_)
plt.subplot(133)
plt.scatter(X[:, 0], X[:, 1], c=km4.labels_)#.astype(np.float))
plt.title("K=4, J=%.2f" % km4.inertia_)
<matplotlib.text.Text at 0x7fe4ad47b710>
Exercises
1. Analyse clusters
Analyse the plot above visually. What would a good value of be?
If you instead consider the inertia, the value of , what would a good value of be?
Explain why there is such difference.
For = 2 why did -means clustering not find the two natural clusters? See the as-
sumptions of -means: http://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_assumptions.html#
example-cluster-plot-kmeans-assumptions-py
Write a function kmeans(X,K) that return an integer vector of the samples labels.
Hierarchical clustering
Hierarchical clustering is an approach to clustering that build hierarchies of clusters in two main approaches:
Agglomerative: A bottom-up strategy, where each observation starts in their own cluster, and pairs of clusters
are merged upwards in the hierarchy.
Divisive: A top-down strategy, where all observations start out in the same cluster, and then the clusters are split
recursively downwards in the hierarchy.
In order to decide which clusters to merge or to split, a measure of dissimilarity between clusters is introduced. More
specific, this comprise a distance measure and a linkage criterion. The distance measure is just what it sounds like,
and the linkage criterion is essentially a function of the distances between points, for instance the minimum distance
between points in two clusters, the maximum distance between points in two clusters, the average distance between
points in two clusters, etc. One particular linkage criterion, the Ward criterion, will be discussed next.
Ward clustering
Ward clustering belongs to the family of agglomerative hierarchical clustering algorithms. This means that they are
based on a bottoms up approach: each sample starts in its own cluster, and pairs of clusters are merged as one moves
up the hierarchy.
In Ward clustering, the criterion for choosing the pair of clusters to merge at each step is the minimum variance
criterion. Wards minimum variance criterion minimizes the total within-cluster variance by each merge. To implement
this method, at each step: find the pair of clusters that leads to minimum increase in total within-cluster variance after
merging. This increase is a weighted squared distance between cluster centers.
The main advantage of agglomerative hierarchical clustering over -means clustering is that you can benefit from
known neighborhood information, for example, neighboring pixels in an image.
iris = datasets.load_iris()
X = iris.data[:, :2] # 'sepal length (cm)''sepal width (cm)'
y_iris = iris.target
plt.figure(figsize=(9, 3))
plt.subplot(131)
plt.scatter(X[:, 0], X[:, 1], c=ward2.labels_)
plt.title("K=2")
plt.subplot(132)
plt.scatter(X[:, 0], X[:, 1], c=ward3.labels_)
plt.title("K=3")
plt.subplot(133)
plt.scatter(X[:, 0], X[:, 1], c=ward4.labels_) # .astype(np.float))
plt.title("K=4")
<matplotlib.text.Text at 0x7fe4ace5cfd0>
The Gaussian mixture model (GMM) is a simple linear superposition of Gaussian components over the data, aimed
at providing a rich class of density models. We turn to a formulation of Gaussian mixtures in terms of discrete latent
variables: the hidden classes to be discovered.
Differences compared to -means:
Whereas the -means algorithm performs a hard assignment of data points to clusters, in which each data
point is associated uniquely with one cluster, the GMM algorithm makes a soft assignment based on posterior
probabilities.
Whereas the classic -means is only based on Euclidean distances, classic GMM use a Mahalanobis distances
that can deal with non-spherical distributions. It should be noted that Mahalanobis could be plugged within an
improved version of -Means clustering. The Mahalanobis distance is unitless and scale-invariant, and takes
into account the correlations of the data set.
The Gaussian mixture distribution can be written as a linear superposition of Gaussians in the form:
() = ( | , )(),
=1
where:
( | , ) is the multivariate Gaussian distribution defined over a -dimensional vector of continuous
variables.
The () are the mixing coefficients also know as the class probability of class , and they sum to one:
=1 () = 1.
To compute the classes parameters: (), , we sum over all samples, by weighting each sample by its re-
sponsibility or contribution to class : ( | ) such that for each point its contribution to all classes sum to one
( | ) = 1. This contribution is the conditional probability of class given : ( | ) (sometimes called the
posterior). It can be computed using Bayes rule:
( | )()
( | ) = (9.2)
()
( | , )()
= (9.3)
=1 ( | , )()
Since the class parameters, (), and , depend on the responsibilities ( | ) and the responsibilities depend on
class parameters, we need a two-step iterative algorithm: the expectation-maximization (EM) algorithm. We discuss
this algorithm next.
Given a Gaussian mixture model, the goal is to maximize the likelihood function with respect to the parameters
(comprised of the means and covariances of the components and the mixing coefficients).
Initialize the means , covariances and mixing coefficients ()
1. E step. For each sample , evaluate the responsibilities for each class using the current parameter values
( | , )()
( | ) =
=1 ( | , )()
2. M step. For each class, re-estimate the parameters using the current responsibilities
1
new
= ( | ) (9.4)
=1
1 new
new
= ( | )( new
)( ) (9.5)
=1
new () = (9.6)
3. Evaluate the log-likelihood
{
}
ln (| , )() ,
=1 =1
and check for convergence of either the parameters or the log-likelihood. If the convergence criterion is not satisfied
return to step 1.
import numpy as np
from sklearn import datasets
import matplotlib.pyplot as plt
import seaborn as sns # nice color
import sklearn
from sklearn.mixture import GaussianMixture
import pystatsml.plot_utils
colors = sns.color_palette()
iris = datasets.load_iris()
X = iris.data[:, :2] # 'sepal length (cm)''sepal width (cm)'
y_iris = iris.target
plt.figure(figsize=(9, 3))
plt.subplot(131)
plt.scatter(X[:, 0], X[:, 1], c=[colors[lab] for lab in gmm2.predict(X)])#,
color=colors)
for i in range(gmm2.covariances_.shape[0]):
pystatsml.plot_utils.plot_cov_ellipse(cov=gmm2.covariances_[i, :], pos=gmm2.means_
[i, :],
plt.subplot(132)
plt.scatter(X[:, 0], X[:, 1], c=[colors[lab] for lab in gmm3.predict(X)])
for i in range(gmm3.covariances_.shape[0]):
pystatsml.plot_utils.plot_cov_ellipse(cov=gmm3.covariances_[i, :], pos=gmm3.means_
[i, :],
plt.subplot(133)
plt.scatter(X[:, 0], X[:, 1], c=[colors[lab] for lab in gmm4.predict(X)]) # .
astype(np.float))
for i in range(gmm4.covariances_.shape[0]):
pystatsml.plot_utils.plot_cov_ellipse(cov=gmm4.covariances_[i, :], pos=gmm4.means_
[i, :],
Model selection
In statistics, the Bayesian information criterion (BIC) is a criterion for model selection among a finite set of models;
the model with the lowest BIC is preferred. It is based, in part, on the likelihood function and it is closely related to
the Akaike information criterion (AIC).
X = iris.data
y_iris = iris.target
bic = list()
#print(X)
ks = np.arange(1, 10)
for k in ks:
gmm = GaussianMixture(n_components=k, covariance_type='full')
gmm.fit(X)
bic.append(gmm.bic(X))
k_chosen = ks[np.argmin(bic)]
plt.plot(ks, bic)
plt.xlabel("k")
plt.ylabel("BIC")
Choose k= 2
TEN
Linear regression models the output, or target variable R as a linear combination of the ( 1)-dimensional
input R( 1) . Let X be the matrix with each row an input vector (with a 1 in the first position), and
similarly let be the -dimensional vector of outputs in the training set, the linear model will predict the y given X
using the parameter vector, or weight vector R according to
y = X + ,
where R are the residuals, or the errors of the prediction. The is found by minimizing an objective function,
which is the loss function, (), i.e. the error measured on the data. This error is the sum of squared errors (SSE)
loss. Minimizing the SSE is the Ordinary Least Square OLS regression as objective function.
OLS() = () (10.1)
= SSE() (10.2)
= ( x )2 (10.3)
= (y X) (y X) (10.4)
= y X22 , (10.5)
Scikit learn offer many models for supervised learning, and they all follow the same application programming interface
(API), namely:
model = Estimator()
model.fit(X, y)
predictions = model.predict(X)
107
Statistics and Machine Learning in Python, Release 0.1
lr = lm.LinearRegression().fit(X, y)
y_pred = lr.predict(X)
print("R-squared =", metrics.r2_score(y, y_pred))
# Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
XX = np.column_stack([xx1.ravel(), xx2.ravel()])
yy = lr.predict(XX)
ax.plot_surface(xx1, xx2, yy.reshape(xx1.shape), color='None')
ax.set_xlabel('TV')
ax.set_ylabel('Radio')
_ = ax.set_zlabel('Sales')
R-squared = 0.897194261083
Coefficients = [ 0.04575482 0.18799423]
Overfitting
In statistics and machine learning, overfitting occurs when a statistical model describes random errors or noise instead
of the underlying relationships. Overfitting generally occurs when a model is excessively complex, such as having too
many parameters relative to the number of observations. A model that has been overfit will generally have poor
predictive performance, as it can exaggerate minor fluctuations in the data.
A learning algorithm is trained using some set of training samples. If the learning algorithm has the capacity to overfit
the training samples the performance on the training sample set will improve while the performance on unseen test
sample set will decline.
The overfitting phenomenon has three main explanations: - excessively complex models, - multicollinearity, and - high
dimensionality.
Model complexity
Complex learners with too many parameters relative to the number of observations may overfit the training dataset.
Multicollinearity
Predictors are highly correlated, meaning that one can be linearly predicted from the others. In this situation the
coefficient estimates of the multiple regression may change erratically in response to small changes in the model or
the data. Multicollinearity does not reduce the predictive power or reliability of the model as a whole, at least not
within the sample data set; it only affects computations regarding individual predictors. That is, a multiple regression
model with correlated predictors can indicate how well the entire bundle of predictors predicts the outcome variable,
but it may not give valid results about any individual predictor, or about which predictors are redundant with respect to
others. In case of perfect multicollinearity the predictor matrix is singular and therefore cannot be inverted. Under these
circumstances, for a general linear model y = X +, the ordinary least-squares estimator, = (X X)1 X y,
does not exist.
An example where correlated predictor may produce an unstable model follows:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
X = np.column_stack([bv, tax])
beta_star = np.array([.1, 0]) # true solution
'''
Since tax and bv are correlated, there is an infinite number of linear combinations
leading to the same prediction.
'''
Multicollinearity between the predictors: business volumes and tax produces unstable models with arbitrary large coef-
ficients.
Dealing with multicollinearity:
Regularisation by e.g. 2 shrinkage: Introduce a bias in the solution by making ( )1 non-singular. See 2
shrinkage.
Feature selection: select a small number of features. See: Isabelle Guyon and Andr Elisseeff An introduction
to variable and feature selection The Journal of Machine Learning Research, 2003.
Feature selection: select a small number of features using 1 shrinkage.
Extract few independent (uncorrelated) features using e.g. principal components analysis (PCA), partial least
squares regression (PLS-R) or regression methods that cut the number of predictors to a smaller set of uncorre-
lated components.
High dimensionality
High dimensions means a large number of input features. Linear predictor associate one parameter to each input
feature, so a high-dimensional situation ( , number of features, is large) with a relatively small number of samples
(so-called large small situation) generally lead to an overfit of the training data. Thus it is generally a bad idea to
add many input features into the learner. This phenomenon is called the curse of dimensionality.
One of the most important criteria to use when choosing a learning algorithm is based on the relative size of and .
Remenber that the covariance matrix X X used in the linear model is a matrix of rank min(, ).
Thus if > the equation system is overparameterized and admit an infinity of solutions that might be specific
to the learning dataset. See also ill-conditioned or singular matrices.
The sampling density of samples in an -dimensional space is proportional to 1/ . Thus a high-
dimensional space becomes very sparse, leading to poor estimations of samples densities.
Another consequence of the sparse sampling in high dimensions is that all sample points are close to an edge
of the sample. Consider data points uniformly distributed in a -dimensional unit ball centered at the origin.
Suppose we consider a nearest-neighbor estimate at the origin. The median distance from the origin to the
closest data point is given by the expression
)1/
1
(
(, ) = 1 .
2
A more complicated expression exists for the mean distance to the closest point. For N = 500, P = 10 , (, ) 0.52,
more than halfway to the boundary. Hence most data points are closer to the boundary of the sample space than to any
other data point. The reason that this presents a problem is that prediction is much more difficult near the edges of the
training sample. One must extrapolate from neighboring sample points rather than interpolate between them. (Source:
T Hastie, R Tibshirani, J Friedman. The Elements of Statistical Learning: Data Mining, Inference, and Prediction.
Second Edition, 2009.)
Structural risk minimization provides a theoretical background of this phenomenon. (See VC dimension.)
See also biasvariance trade-off.
import seaborn # nicer plots
def fit_on_increasing_size(model):
n_samples = 100
n_features_ = np.arange(10, 800, 20)
r2_train, r2_test, snr = [], [], []
for n_features in n_features_:
# Sample the dataset (* 2 nb of samples)
n_features_info = int(n_features/10)
np.random.seed(42) # Make reproducible
X = np.random.randn(n_samples * 2, n_features)
beta = np.zeros(n_features)
beta[:n_features_info] = 1
Xbeta = np.dot(X, beta)
eps = np.random.randn(n_samples * 2)
y = Xbeta + eps
# Split the dataset into train and test sample
Xtrain, Xtest = X[:n_samples, :], X[n_samples:, :],
ytrain, ytest = y[:n_samples], y[n_samples:]
# fit/predict
lr = model.fit(Xtrain, ytrain)
y_pred_train = lr.predict(Xtrain)
y_pred_test = lr.predict(Xtest)
snr.append(Xbeta.std() / eps.std())
r2_train.append(metrics.r2_score(ytrain, y_pred_train))
r2_test.append(metrics.r2_score(ytest, y_pred_test))
return n_features_, np.array(r2_train), np.array(r2_test), np.array(snr)
argmax = n_features[np.argmax(r2_test)]
# plot
fig, axis = plt.subplots(1, 2, figsize=(9, 3))
Exercises
What is n_features_info?
Give the equation of the generative model.
What is modified by the loop?
What is the SNR?
Comment the graph above, in terms of training and test performances:
How does the train and test performance changes as a function of ?
Is it the expected results when compared to the SNR?
What can you conclude?
Overfitting generally leads to excessively complex weight vectors, accounting for noise or spurious correlations within
predictors. To avoid this phenomenon the learning should constrain the solution in order to fit a global pattern. This
constraint will reduce (bias) the capacity of the learning algorithm. Adding such a penalty will force the coefficients
to be small, i.e. to shrink them toward zeros.
Therefore the loss function () (generally the SSE) is combined with a penalty function () leading to the general
form:
Penalized() = () + ()
The respective contribution of the loss and the penalty is controlled by the regularization parameter .
Ridge regression impose a 2 penalty on the coefficients, i.e. it penalizes with the Euclidean norm of the coefficients
while minimizing SSE. The objective function becomes:
Ridge() = y X22 + 22 .
Ridge() = 0 (10.6)
(y X) (y X) + = 0
( )
(10.7)
(y y 2 X y + X X + ) = 0
( )
(10.8)
2X y + 2X X + 2 = 0 (10.9)
X y + (X X + I) = 0 (10.10)
(X X + I) = X y (10.11)
1
= (X X + I) X y (10.12)
The solution adds a positive constant to the diagonal of X X before inversion. This makes the problem non-
singular, even if X X is not of full rank, and was the main motivation behind ridge regression.
Increasing shrinks the coefficients toward 0.
This approach penalizes the objective function by the Euclidian (:math:ell_2) norm of the coefficients such
that solutions with large coefficients become unattractive.
The ridge penalty shrinks the coefficients toward zero. The figure illustrates: the OLS solution on the left. The 1 and
2 penalties in the middle pane. The penalized OLS in the right pane. The right pane shows how the penalties shrink
the coefficients toward zero. The black points are the minimum found in each case, and the white points represents the
true solution used to generate the data.
# lambda is alpha!
mod = lm.Ridge(alpha=10)
argmax = n_features[np.argmax(r2_test)]
# plot
fig, axis = plt.subplots(1, 2, figsize=(9, 3))
Exercice
Lasso regression penalizes the coefficients by the 1 norm. This constraint will reduce (bias) the capacity of the
learning algorithm. To add such a penalty forces the coefficients to be small, i.e. it shrinks them toward zero. The
objective function to minimize becomes:
This penalty forces some coefficients to be exactly zero, providing a feature selection property.
# lambda is alpha !
mod = lm.Lasso(alpha=.1)
argmax = n_features[np.argmax(r2_test)]
# plot
fig, axis = plt.subplots(1, 2, figsize=(9, 3))
Occams razor
Occams razor (also written as Ockhams razor, and lex parsimoniae in Latin, which means law of parsimony) is a
problem solving principle attributed to William of Ockham (1287-1347), who was an English Franciscan friar and
scholastic philosopher and theologian. The principle can be interpreted as stating that among competing hypotheses,
the one with the fewest assumptions should be selected.
Principle of parsimony
The simplest of two competing theories is to be preferred. Definition of parsimony: Economy of explanation in
conformity with Occams razor.
Among possible models with similar loss, choose the simplest one:
Choose the model with the smallest coefficient vector, i.e. smallest 2 (2 ) or 1 (1 ) norm of , i.e. 2 or
1 penalty. See also bias-variance tradeoff.
Choose the model that uses the smallest number of predictors. In other words, choose the model that has many
predictors with zero weights. Two approaches are available to obtain this: (i) Perform a feature selection as a
preprocessing prior to applying the learning algorithm, or (ii) embed the feature selection procedure within the
learning process.
The penalty based on the 1 norm promotes sparsity (scattered, or not dense): it forces many coefficients to be exactly
zero. This also makes the coefficient vector scattered.
The figure bellow illustrates the OLS loss under a constraint acting on the 1 norm of the coefficient vector. I.e., it
illustrates the following optimization problem:
minimize y X22
subject to 1 1.
Optimization issues
Section to be completed
No more closed-form solution.
Convex but not differentiable.
Requires specific optimization algorithms, such as the fast iterative shrinkage-thresholding algorithm (FISTA):
Amir Beck and Marc Teboulle, A Fast Iterative Shrinkage-Thresholding Algorithm for Linear Inverse Problems
SIAM J. Imaging Sci., 2009.
The Elastic-net estimator combines the 1 and 2 penalties, and results in the problem to
Enet() = y X 22 + 1 + (1 ) 22 ,
( )
(10.14)
Rationale
If there are groups of highly correlated variables, Lasso tends to arbitrarily select only one from each group.
These models are difficult to interpret because covariates that are strongly associated with the outcome are
not included in the predictive model. Conversely, the elastic net encourages a grouping effect, where strongly
correlated predictors tend to be in or out of the model together.
Studies on real world data and simulation studies show that the elastic net often outperforms the lasso, while
enjoying a similar sparsity of representation.
argmax = n_features[np.argmax(r2_test)]
# plot
fig, axis = plt.subplots(1, 2, figsize=(9, 3))
ELEVEN
LINEAR CLASSIFICATION
A linear classifier achieves its classification decision of based on the value of a linear combination of the input
features of a given sample , such that
= ( ),
( , ),
This geometric method does not make any probabilistic assumptions, instead it relies on distances. It looks for the
linear projection of the data points onto a vector, , that maximizes the between/within variance ratio, denoted ().
Under a few assumptions, it will provide the same results as linear discriminant analysis (LDA), explained below.
Suppose two classes of observations, 0 and 1 , have means 0 and 1 and the same total within-class scatter
(covariance) matrix,
= ( 0 )( 0 ) + ( 1 )( 1 ) (11.1)
0 1
= , (11.2)
= (1 0 )(1 0 ) .
The linear combination of features have means for = 0, 1, and variance . Fisher defined
the separation between these two distributions to be the ratio of the variance between the classes to the variance within
121
Statistics and Machine Learning in Python, Release 0.1
the classes:
2
between
Fisher () = 2 (11.3)
within
( 1 0 )2
= (11.4)
( (1 0 ))2
= (11.5)
(1 0 )(1 0 )
= (11.6)
= . (11.7)
In the two-class case, the maximum separation occurs by a projection on the (1 0 ) using the Mahalanobis metric
1 , so that
1 (1 0 ).
Demonstration
Fisher () = 0
( )
=0
( )(2 ) ( )(2 ) = 0
( )( ) = ( )( )
= ( )
= ( )
1 = .
Since we do not care about the magnitude of , only its direction, we replaced the scalar factor
( )/( ) by .
In the multiple-class case, the solutions are determined by the eigenvectors of 1 that correspond to the
1 largest eigenvalues.
However, in the two-class case (in which = (1 0 )(1 0 ) ) it is easy to show that = 1 (1 0 )
is the unique eigenvector of 1 :
1 (1 0 )(1 0 ) =
1 (1 0 )(1 0 ) 1 (1 0 ) = 1 (1 0 ),
1 (1 0 ).
The separating hyperplane is a 1-dimensional hyper surface, orthogonal to the projection vector, . There is no
single best way to find the origin of the plane along , or equivalently the classification threshold that determines
whether a point should be classified as belonging to 0 or to 1 . However, if the projected points have roughly
the same distribution, then the threshold can be chosen as the hyperplane exactly between the projections of the two
means, i.e. as
1
= (1 0 ).
2
Exercise
Write a class FisherLinearDiscriminant that implements the Fishers linear discriminant analysis. This class
must be compliant with the scikit-learn API by providing two methods: - fit(X,y) which fits the model and returns
the object itself; - predict(X) which returns a vector of the predicted values. Apply the object on the dataset
presented for the LDA.
Linear discriminant analysis (LDA) is a probabilistic generalization of Fishers linear discriminant. It uses Bayes rule
to fix the threshold based on prior probabilities of classes.
1. First compute the The class-conditional distributions of given class : (| ) = (| , ). Where
(| , ) is the multivariate Gaussian distribution defined over a P-dimensional vector of continuous
variables, which is given by
1 1
(| , ) = exp{ ( ) 1 ( )}
(2)/2 | |1/2 2
2. Estimate the prior probabilities of class , ( ) = / .
3. Compute posterior probabilities (ie. the probability of a each class given a sample) combining conditional
with priors using Bayes rule:
( )(| )
( |) =
()
Where () is the marginal distribution obtained by suming of classes: As usual, the denominator in Bayes theorem
can be found in terms of the quantities appearing in the numerator, because
() = (| )( )
Exercise
%matplotlib inline
import numpy as np
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
# Dataset
n_samples, n_features = 100, 2
mean0, mean1 = np.array([0, 0]), np.array([0, 2])
Cov = np.array([[1, .8],[.8, 1]])
np.random.seed(42)
errors = y_pred_lda != y
print("Nb errors=%i, error rate=%.2f" % (errors.sum(), errors.sum() / len(y_pred_
lda)))
Logistic regression
Logistic regression is called a generalized linear models. ie.: it is a linear model with a link function that maps the
output of linear multiple regression to the posterior probability of each class ( |) [0, 1] using the logistic sigmoid
function:
1
( |, ) =
1 + exp( )
min () =
( |, )
In the two-class case the algorithms simplify considerably by coding the two-classes (0 and 1 ) via a 0/1 response
. Indeed, since (0 |, ) = 1 (1 |, ), the log-likelihood can be re-witten:
log () = { log (1 |, ) + (1 ) log(1 (1 |, ))} (11.8)
log () = { log(1 + exp )} (11.9)
(11.10)
Logistic regression is a discriminative model since it focuses only on the posterior probability of each class ( |).
It only requires to estimate the weight of the vector. Thus it should be favoured over LDA with many input
features. In small dimension and balanced situations it would provide similar predictions than LDA.
However imbalanced group sizes cannot be explicitly controlled. It can be managed using a reweighting of the input
samples.
logreg.fit(X, y)
y_pred_logreg = logreg.predict(X)
errors = y_pred_logreg != y
print("Nb errors=%i, error rate=%.2f" % (errors.sum(), errors.sum() / len(y_pred_
logreg)))
print(logreg.coef_)
Exercise
Explore the Logistic Regression parameters and proposes a solution in cases of highly imbalanced training
dataset 1 0 when we know that in reality both classes have the same probability (1 ) = (0 ).
Overfitting
VC dimension (for VapnikChervonenkis dimension) is a measure of the capacity (complexity, expressive power,
richness, or flexibility) of a statistical classification algorithm, defined as the cardinality of the largest set of points that
the algorithm can shatter.
Theorem: Linear classifier in have VC dimension of +1. Hence in dimension two ( = 2) any random partition
of 3 points can be learned.
When the matrix is not full rank or , the The Fisher most discriminant projection estimate of the is not
unique. This can be solved using a biased version of :
= +
where is the identity matrix. This leads to the regularized (ridge) estimator of the Fishers linear discriminant
analysis:
( + )1 (1 0 )
Increasing will:
Shrinks the coefficients toward zero.
The covariance will converge toward the diagonal matrix, reducing the contribution of the pairwise covariances.
The objective function to be minimized is now the combination of the logistic loss log () with a penalty of the L2
norm of the weights vector. In the two-class case, using the 0/1 coding we obtain:
# Dataset
# Build a classification task using 3 informative features
from sklearn import datasets
X, y = datasets.make_classification(n_samples=100,
n_features=20,
n_informative=3,
n_redundant=0,
n_repeated=0,
n_classes=2,
random_state=0,
shuffle=False)
lr.fit(X, y)
y_pred_lr = lr.predict(X)
errors = y_pred_lr != y
print("Nb errors=%i, error rate=%.2f" % (errors.sum(), errors.sum() / len(y)))
print(lr.coef_)
The objective function to be minimized is now the combination of the logistic loss log () with a penalty of the L1
norm of the weights vector. In the two-class case, using the 0/1 coding we obtain:
lrl1.fit(X, y)
y_pred_lrl1 = lrl1.predict(X)
errors = y_pred_lrl1 != y
print("Nb errors=%i, error rate=%.2f" % (errors.sum(), errors.sum() / len(y_pred_
lrl1)))
print(lrl1.coef_)
Support Vector Machine seek for separating hyperplane with maximum margin to enforce robustness against noise.
Like logistic regression it is a discriminative method that only focuses of predictions.
Here we present the non separable case of Maximum Margin Classifiers with 1 coding (ie.: {1, +1}). In the
next figure the legend aply to samples of dot class.
So linear SVM is closed to Ridge logistic regression, using the hinge loss instead of the logistic loss. Both will provide
very similar predictions.
svmlin = svm.LinearSVC()
# Remark: by default LinearSVC uses squared_hinge as loss
svmlin.fit(X, y)
y_pred_svmlin = svmlin.predict(X)
errors = y_pred_svmlin != y
print("Nb errors=%i, error rate=%.2f" % (errors.sum(), errors.sum() / len(y_pred_
svmlin)))
print(svmlin.coef_)
Linear SVM for classification (also called SVM-C or SVC) with l1-regularization
min Lasso linear SVM () = ||||1 +
with ( ) 1
svmlinl1.fit(X, y)
y_pred_svmlinl1 = svmlinl1.predict(X)
errors = y_pred_svmlinl1 != y
print("Nb errors=%i, error rate=%.2f" % (errors.sum(), errors.sum() / len(y_pred_
svmlinl1)))
print(svmlinl1.coef_)
Exercise
Compare predictions of Logistic regression (LR) and their SVM counterparts, ie.: L2 LR vs L2 SVM and L1 LR vs
L1 SVM
The objective function to be minimized is now the combination of the logistic loss log () or the hinge loss with
combination of L1 and L2 penalties. In the two-class case, using the 0/1 coding we obtain:
X, y = datasets.make_classification(n_samples=100,
n_features=20,
n_informative=3,
n_redundant=0,
n_repeated=0,
n_classes=2,
random_state=0,
shuffle=False)
Exercise
source: https://en.wikipedia.org/wiki/Sensitivity_and_specificity
Imagine a study evaluating a new test that screens people for a disease. Each person taking the test either has or
does not have the disease. The test outcome can be positive (classifying the person as having the disease) or negative
(classifying the person as not having the disease). The test results for each subject may or may not match the subjects
actual status. In that setting:
True positive (TP): Sick people correctly identified as sick
False positive (FP): Healthy people incorrectly identified as sick
True negative (TN): Healthy people correctly identified as healthy
False negative (FN): Sick people incorrectly identified as healthy
Accuracy (ACC):
ACC = (TP + TN) / (TP + FP + FN + TN)
Sensitivity (SEN) or recall of the positive class or true positive rate (TPR) or hit rate:
SEN = TP / P = TP / (TP+FN)
Specificity (SPC) or recall of the negative class or true negative rate:
SPC = TN / N = TN / (TN+FP)
Precision or positive predictive value (PPV):
PPV = TP / (TP + FP)
Balanced accuracy (bACC):is a useful performance measure is the balanced accuracy which avoids inflated
performance estimates on imbalanced datasets (Brodersen, et al. (2010). The balanced accuracy and its pos-
terior distribution). It is defined as the arithmetic mean of sensitivity and specificity, or the average accuracy
obtained on either class:
bACC = 1/2 * (SEN + SPC)
F1 Score (or F-score) which is a weighted average of precision and recall are usefull to deal with imballaced
datasets
The four outcomes can be formulated in a 22 contingency table or confusion matrix https://en.wikipedia.org/wiki/
Sensitivity_and_specificity
For more precision see: http://scikit-learn.org/stable/modules/model_evaluation.html
metrics.accuracy_score(y_true, y_pred)
metrics.recall_score(y_true, y_pred)
# Balanced accuracy
b_acc = recalls.mean()
Some classifier may have found a good discriminative projection . However if the threshold to decide the final
predicted class is poorly adjusted, the performances will highlight an high specificity and a low sensitivity or the
contrary.
In this case it is recommended to use the AUC of a ROC analysis which basically provide a measure of over-
lap of the two classes when points are projected on the discriminative axis. For more detail on ROC and AUC
see:https://en.wikipedia.org/wiki/Receiver_operating_characteristic.
print("Predictions:", y_pred)
metrics.accuracy_score(y_true, y_pred)
Predictions: [0 0 0 0 0 0 0 0]
Recalls: [ 1. 0.]
AUC: 1.0
/usr/local/anaconda3/lib/python3.5/site-packages/sklearn/metrics/classification.py:
1113: UndefinedMetricWarning: Precision and F-score are ill-defined and being set
Imbalanced classes
Learning with discriminative (logistic regression, SVM) methods is generally based on minimizing the misclassifica-
tion of training samples, which may be unsuitable for imbalanced datasets where the recognition might be biased in
favor of the most numerous class. This problem can be addressed with a generative approach, which typically requires
more parameters to be determined leading to reduced performances in high dimension.
Dealing with imbalanced class may be addressed by three main ways (see Japkowicz and Stephen (2002) for a review),
resampling, reweighting and one class learning.
In sampling strategies, either the minority class is oversampled or majority class is undersampled or some combina-
tion of the two is deployed. Undersampling (Zhang and Mani, 2003) the majority class would lead to a poor usage of
the left-out samples. Sometime one cannot afford such strategy since we are also facing a small sample size problem
even for the majority class. Informed oversampling, which goes beyond a trivial duplication of minority class samples,
requires the estimation of class conditional distributions in order to generate synthetic samples. Here generative mod-
els are required. An alternative, proposed in (Chawla et al., 2002) generate samples along the line segments joining
any/all of the k minority class nearest neighbors. Such procedure blindly generalizes the minority area without re-
gard to the majority class, which may be particularly problematic with high-dimensional and potentially skewed class
distribution.
Reweighting, also called cost-sensitive learning, works at an algorithmic level by adjusting the costs of the various
classes to counter the class imbalance. Such reweighting can be implemented within SVM (Chang and Lin, 2001) or
logistic regression (Friedman et al., 2010) classifiers. Most classifiers of Scikit learn offer such reweighting possibili-
ties.
The clas\boldsymbol{S_W}eight parameter can be positioned into the "balanced" mode which uses the
values of to automatically adjust weights inversely proportional to class frequencies in the input data as /(2 ).
import numpy as np
from sklearn import linear_model
from sklearn import datasets
from sklearn import metrics
import matplotlib.pyplot as plt
# dataset
X, y = datasets.make_classification(n_samples=500,
n_features=5,
n_informative=2,
n_redundant=0,
n_repeated=0,
n_classes=2,
random_state=1,
shuffle=False)
Ximb = X[subsample_idx, :]
yimb = y[subsample_idx]
print(*["#samples of class %i = %i;" % (lev, np.sum(yimb == lev)) for lev in
np.unique(yimb)])
lr_inter_reweight.fit(Ximb, yimb)
p, r, f, s = metrics.precision_recall_fscore_support(yimb,
lr_inter_reweight.predict(Ximb))
print("SPC: %.3f; SEN: %.3f" % tuple(r))
print('# => The predictions are balanced in sensitivity and specificity\n')
^
SyntaxError: unexpected character after line continuation character
TWELVE
SVM are based kernel methods require only a user-specified kernel function ( , ), i.e., a similarity function over
pairs of data points ( , ) into kernel (dual) space on which learning algorithms operate linearly, i.e. every operation
on points is a linear combination of ( , ).
Outline of the SVM algorithm:
1. Map points into kernel space using a kernel function: (, .).
2. Learning algorithms operate linearly by dot product into high-kernel space (., ) (., ).
Using the kernel trick (Mercers Theorem) replace dot product in hgh dimensional space by a simpler
operation such that (., ) (., ) = ( , ). Thus we only need to compute a similarity measure
for each pairs of point and store in a Gram matrix.
Finally, The learning process consist of estimating the of the decision function that maximises the hinge
loss (of ()) plus some penalty when applied on all training points.
( )
() = sign ( , ) .
One of the most commonly used kernel is the Radial Basis Function (RBF) Kernel. For a pair of points , the RBF
kernel is defined as:
2
( )
( , ) = exp (12.1)
2 2
= exp 2
( )
(12.2)
Where (or ) defines the kernel width parameter. Basically, we consider a Gaussian function centered on each
training sample . it has a ready interpretation as a similarity measure as it decreases with squared Euclidean distance
between the two feature vectors.
Non linear SVM also exists for regression problems.
import numpy as np
from sklearn.svm import SVC
from sklearn import datasets
137
Statistics and Machine Learning in Python, Release 0.1
# dataset
X, y = datasets.make_classification(n_samples=10, n_features=2,n_redundant=0,
n_classes=2,
random_state=1,
shuffle=False)
clf = SVC(kernel='rbf')#, gamma=1)
clf.fit(X, y)
print("#Errors: %i" % np.sum(y != clf.predict(X)))
clf.decision_function(X)
# Usefull internals:
# Array of support vectors
clf.support_vectors_
#Errors: 0
True
Random forest
A random forest is a meta estimator that fits a number of decision tree learners on various sub-samples of the dataset
and use averaging to improve the predictive accuracy and control over-fitting.
A tree can be learned by splitting the training dataset into subsets based on an features value test.
Each internal node represents a test on an feature resulting on the split of the current sample. At each step the
algorithm selects the feature and a cutoff value that maximises a given metric. Different metrics exist for regression
tree (target is continuous) or classification tree (the target is qualitative).
This process is repeated on each derived subset in a recursive manner called recursive partitioning. The recursion is
completed when the subset at a node has all the same value of the target variable, or when splitting no longer adds
value to the predictions. This general principle is implemented by many recursive partitioning tree algorithms.
Decision trees are simple to understand and interpret however they tend to overfit the data. However decision trees
tend to overfit the training set. Leo Breiman propose random forest to deal with this issue.
THIRTEEN
RESAMPLING METHODS
The training error can be easily calculated by applying the statistical learning method to the observations used in
its training. But because of overfitting, the training error rate can dramatically underestimate the error that would be
obtained on new samples.
The test error is the average error that results from a learning method to predict the response on a new samples that
is, on samples that were not used in training the method. Given a data set, the use of a particular learning method is
warranted if it results in a low test error. The test error can be easily calculated if a designated test set is available.
Unfortunately, this is usually not the case.
Thus the original dataset is generally split in a training and a test (or validation) data sets. Large training set (80%)
small test set (20%) might provide a poor estimation of the predictive performances. On the contrary, large test set
and small training set might produce a poorly estimated learner. This is why, on situation where we cannot afford such
split, it recommended to use cross-Validation scheme to estimate the predictive power of a learning algorithm.
Cross-Validation (CV)
Cross-Validation scheme randomly divides the set of observations into groups, or folds, of approximately equal
size. The first fold is treated as a validation set, and the method () is fitted on the remaining union of 1 folds:
( ( , )).
The mean error measure (generally a loss function) is evaluated of the on the observations in the held-out fold. For
each sample we consider the model estimated on the data set that did not contain it, noted (). This procedure is
repeated times; each time, a different group of observations is treated as a test set. Then we compare the predicted
value ( (() ) = ) with true value using a Error function (). Then the cross validation estimate of prediction
error is
1 ( )
( ) = , (() ) .
This validation scheme is known as the K-Fold CV. Typical choices of are 5 or 10, [Kohavi 1995]. The extreme
case where = is known as leave-one-out cross-validation, LOO-CV.
CV for regression
Usually the error function () is the r-squared score. However other function could be used.
141
Statistics and Machine Learning in Python, Release 0.1
import numpy as np
from sklearn import datasets
import sklearn.linear_model as lm
import sklearn.metrics as metrics
from sklearn.cross_validation import KFold
X, y = datasets.make_regression(n_samples=100, n_features=100,
n_informative=10, random_state=42)
model = lm.Ridge(alpha=10)
Train r2:0.99
Test r2:0.72
# provide a cv
scores = cross_val_score(estimator=model, X=X, y=y, cv=cv)
print("Test r2:%.2f" % scores.mean())
Test r2:0.73
Test r2:0.73
CV for classification
With classification problems it is essential to sample folds where each set contains approximately the same percent-
age of samples of each target class as the complete set. This is called stratification. In this case, we will use
StratifiedKFold with is a variation of k-fold which returns stratified folds.
Usually the error function () are, at least, the sensitivity and the specificity. However other function could be used.
import numpy as np
from sklearn import datasets
import sklearn.linear_model as lm
import sklearn.metrics as metrics
from sklearn.cross_validation import StratifiedKFold
X, y = datasets.make_classification(n_samples=100, n_features=100,
n_informative=10, random_state=42)
model = lm.LogisticRegression(C=1)
cv = StratifiedKFold(y, n_folds=5)
y_test_pred = np.zeros(len(y))
y_train_pred = np.zeros(len(y))
Test ACC:0.81
Note that with Scikit-learn user-friendly function we average the scores average obtained on individual folds which
may provide slightly different results that the overall average presented earlier.
2. Model selection: estimating the performance of different models in order to choose the best one. One special
case of model selection is the selection models hyper parameters. Indeed remember that most of learning
algorithm have a hyper parameters (typically the regularization parameter) that has to be set.
Generally we must address the two problems simultaneously. The usual approach for both problems is to randomly
divide the dataset into three parts: a training set, a validation set, and a test set.
The training set (train) is used to fit the models;
the validation set (val) is used to estimate prediction error for model selection or to determine the hyper param-
eters over a grid of possible values.
the test set (test) is used for assessment of the generalization error of the final chosen model.
Model selection of the best hyper parameters over a grid of possible values
For each possible values of hyper parameters :
1. Fit the learner on training set: ( , , )
2. Evaluate the model on the validation set and keep the parameter(s) that minimises the error measure
* = arg min ( ( ), , )
3. Refit the learner on all training + validation data using the best hyper parameters: *
( , , * )
4. ** Model assessment ** of * on the test set: ( * ( ), )
Most of time, we cannot afford such three-way split. Thus, again we will use CV, but in this case we need two nested
CVs.
One outer CV loop, for model assessment. This CV performs splits of the dataset into training plus validation
( , ) set and a test set ,
One inner CV loop, for model selection. For each run of the outer loop, the inner loop loop performs splits of
dataset ( , ) into training set: (, , , ) and a validation set: (, , , ).
Note that the inner CV loop combined with the learner form a new learner with an automatic model (parameter)
selection procedure. This new learner can be easily constructed using Scikit-learn. The learned is wrapped inside a
GridSearchCV class.
Then the new learned can be plugged into the classical outer CV loop.
import numpy as np
from sklearn import datasets
import sklearn.linear_model as lm
from sklearn.grid_search import GridSearchCV
import sklearn.metrics as metrics
from sklearn.cross_validation import KFold
# Dataset
noise_sd = 10
# Use this to tune the noise parameter such that snr < 5
print("SNR:", np.std(np.dot(X, coef)) / noise_sd)
# Warp
model = GridSearchCV(lm.ElasticNet(max_iter=10000), param_grid, cv=5)
SNR: 2.63584694464
Train r2:0.96
{'l1_ratio': 0.9, 'alpha': 1.0}
Train r2:1.00
Test r2:0.62
Selected alphas: [{'l1_ratio': 0.9, 'alpha': 0.001}, {'l1_ratio': 0.9, 'alpha': 0.001}
, {'l1_ratio': 0.9, 'alpha': 0.001}, {'l1_ratio': 0.9, 'alpha': 0.01}, {'l1_ratio':
Test r2:0.55
Sklearn will automatically select a grid of parameters, most of time use the defaults values.
n_jobs is the number of CPUs to use during the cross validation. If -1, use all the CPUs.
# Dataset
X, y, coef = datasets.make_regression(n_samples=50, n_features=100, noise=10,
n_informative=2, random_state=42, coef=True)
X, y = datasets.make_classification(n_samples=100, n_features=100,
n_informative=10, random_state=42)
Random Permutations
A permutation test is a type of non-parametric randomization test in which the null distribution of a test statistic is
estimated by randomly permuting the observations.
Permutation tests are highly attractive because they make no assumptions other than that the observations are indepen-
dent and identically distributed under the null hypothesis.
1. Compute a observed statistic on the data.
2. Use randomization to compute the distribution of under the null hypothesis: Perform random permutation
of the data. For each sample of permuted data, the data compute the statistic . This procedure provides the
distribution of under the null hypothesis 0 : (|0 )
3. Compute the p-value = ( > |0 ) |{ > }|, where s include .
np.random.seed(42)
x = np.random.normal(loc=10, scale=1, size=100)
y = x + np.random.normal(loc=-3, scale=3, size=100) # snr = 1/2
# Plot
# Re-weight to obtain distribution
weights = np.ones(perms.shape[0]) / perms.shape[0]
plt.hist([perms[perms >= perms[0]], perms], histtype='stepfilled',
bins=100, label=["t>t obs (p-value)", "t<t obs"],
weights=[weights[perms >= perms[0]], weights])
_ = plt.legend(loc="upper left")
Exercise
Given the logistic regression presented above and its validation given a 5 folds CV.
1. Compute the p-value associated with the prediction accuracy using a permutation test.
2. Compute the p-value associated with the prediction accuracy using a parametric test.
Bootstrapping
Bootstrapping is a random sampling with replacement strategy which provides an non-parametric method to assess
the variability of performances scores such standard errors or confidence intervals.
A great advantage of bootstrap is its simplicity. It is a straightforward way to derive estimates of standard errors
and confidence intervals for complex estimators of complex parameters of the distribution, such as percentile points,
proportions, odds ratio, and correlation coefficients.
import numpy as np
from sklearn import datasets
import sklearn.linear_model as lm
import sklearn.metrics as metrics
import pandas as pd
# Regression dataset
n_features = 5
n_features_info = 2
n_samples = 100
X = np.random.randn(n_samples, n_features)
beta = np.zeros(n_features)
beta[:n_features_info] = 1
Xbeta = np.dot(X, beta)
eps = np.random.randn(n_samples)
y = Xbeta + eps
# Bootstrap loop
nboot = 100 # !! Should be at least 1000
scores_names = ["r2"]
scores_boot = np.zeros((nboot, len(scores_names)))
coefs_boot = np.zeros((nboot, X.shape[1]))
orig_all = np.arange(X.shape[0])
for boot_i in range(nboot):
boot_tr = np.random.choice(orig_all, size=len(orig_all), replace=True)
boot_te = np.setdiff1d(orig_all, boot_tr, assume_unique=False)
Xtr, ytr = X[boot_tr, :], y[boot_tr]
Xte, yte = X[boot_te, :], y[boot_te]
model.fit(Xtr, ytr)
y_pred = model.predict(Xte).ravel()
scores_boot[boot_i, :] = metrics.r2_score(yte, y_pred)
coefs_boot[boot_i, :] = model.coef_
coefs_boot = pd.DataFrame(coefs_boot)
coefs_stat = coefs_boot.describe(percentiles=[.99, .95, .5, .1, .05, 0.01])
print("Coefficients distribution")
print(coefs_stat)
FOURTEEN
Data preprocessing
Sources: http://www.faqs.org/faqs/ai-faq/neural-nets/part2/section-16.html
To be done
import pandas as pd
Sources:
http://scikit-learn.org/stable/modules/preprocessing.html
http://stats.stackexchange.com/questions/111017/question-about-standardizing-in-ridge-regression
Standardizing or mean removal and variance scaling, is not systematic. For example multiple linear regression does
not require it. However it is a good practice in many cases:
The variable combination method is sensitive scales. If the input variables are combined via a distance
function (such as Euclidean distance) in an RBF network, standardizing inputs can be crucial. The contribution
of an input will depend heavily on its variability relative to other inputs. If one input has a range of 0 to 1,
while another input has a range of 0 to 1,000,000, then the contribution of the first input to the distance will be
swamped by the second input.
Regularized learning algorithm. Lasso or Ridge regression regularize the linear regression by imposing a
penalty on the size of coefficients. Thus the coefficients are shrunk toward zero and toward each other. But
when this happens and if the independent variables does not have the same scale, the shrinking is not fair. Two
independent variables with different scales will have different contributions to the penalized terms, because the
penalized term is norm (a sum of squares, or absolute values) of all the coefficients. To avoid such kind of
problems, very often, the independent variables are centered and scaled in order to have variance 1.
import numpy as np
# dataset
np.random.seed(42)
n_samples, n_features, n_features_info = 100, 5, 3
151
Statistics and Machine Learning in Python, Release 0.1
X = np.random.randn(n_samples, n_features)
beta = np.zeros(n_features)
beta[:n_features_info] = 1
Xbeta = np.dot(X, beta)
eps = np.random.randn(n_samples)
y = Xbeta + eps
import sklearn.linear_model as lm
from sklearn import preprocessing
from sklearn.cross_validation import cross_val_score
Test R2:0.77
Scikit-learn pipelines
Sources: http://scikit-learn.org/stable/modules/pipeline.html
Note that statistics such as the mean and standard deviation are computed from the training data, not from the validation
or test data. The validation and test data must be standardized using the statistics computed from the training data.
Thus Standardization should be merged together with the learner using a Pipeline.
Pipeline chain multiple estimators into one. All estimators in a pipeline, except the last one, must have the fit() and
transform() methods. The last must implement the fit() and predict() methods.
# or
from sklearn.pipeline import Pipeline
model = Pipeline([('standardscaler', preprocessing.StandardScaler()),
('lassocv', lm.LassoCV())])
Test r2:0.77
Features selection
An alternative to features selection based on 1 penalty is to use a preprocessing stp of univariate feature selection.
Such methods, called filters, are a simple, widely used method for supervised dimension reduction [26]. Filters are
univariate methods that rank features according to their ability to predict the target, independently of other features.
This ranking may be based on parametric (e.g., t-tests) or nonparametric (e.g., Wilcoxon tests) statistical methods.
Filters are computationally efficient and more robust to overfitting than multivariate methods. However, they are blind
to feature interrelations, a problem that can be addressed only with multivariate selection such as learning with 1
penalty.
import numpy as np
import sklearn.linear_model as lm
from sklearn import preprocessing
from sklearn.cross_validation import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
from sklearn.pipeline import Pipeline
np.random.seed(42)
n_samples, n_features, n_features_info = 100, 100, 3
X = np.random.randn(n_samples, n_features)
beta = np.zeros(n_features)
beta[:n_features_info] = 1
Xbeta = np.dot(X, beta)
eps = np.random.randn(n_samples)
y = Xbeta + eps
Now we combine standardization of input features, feature selection and learner with hyper-parameter within a pipeline
which is warped in a grid search procedure to select the best hyperparameters based on a (inner)CV. The overall is
plugged in an outer CV.
import numpy as np
from sklearn import datasets
import sklearn.linear_model as lm
from sklearn import preprocessing
from sklearn.cross_validation import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
from sklearn.pipeline import Pipeline
from sklearn.grid_search import GridSearchCV
import sklearn.metrics as metrics
# Datasets
n_samples, n_features, noise_sd = 100, 100, 20
X, y, coef = datasets.make_regression(n_samples=n_samples, n_features=n_features,
noise=noise_sd, n_informative=5,
random_state=42, coef=True)
# Use this to tune the noise parameter such that snr < 5
print("SNR:", np.std(np.dot(X, coef)) / noise_sd)
print("=============================")
print("== Basic linear regression ==")
print("=============================")
print("==============================================")
print("== Scaler + anova filter + ridge regression ==")
print("==============================================")
anova_ridge = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('selectkbest', SelectKBest(f_regression)),
('ridge', lm.Ridge())
])
param_grid = {'selectkbest__k':np.arange(10, 110, 10),
print("----------------------------")
print("-- Parallelize outer loop --")
print("----------------------------")
print("=====================================")
print("== Scaler + Elastic-net regression ==")
print("=====================================")
print("----------------------------")
print("-- Parallelize outer loop --")
print("----------------------------")
enet = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('enet', lm.ElasticNet(max_iter=10000)),
])
param_grid = {'enet__alpha':alphas ,
'enet__l1_ratio':l1_ratio}
enet_cv = GridSearchCV(enet, cv=5, param_grid=param_grid)
%time scores = cross_val_score(estimator=enet_cv, X=X, y=y, cv=5, n_jobs=-1)
print("Test r2:%.2f" % scores.mean())
print("-----------------------------------------------")
print("-- Parallelize outer loop + built-in CV --")
print("-- Remark: scaler is only done on outer loop --")
print("-----------------------------------------------")
enet_cv = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('enet', lm.ElasticNetCV(max_iter=10000, l1_ratio=l1_ratio, alphas=alphas)),
])
SNR: 3.28668201676
=============================
== Basic linear regression ==
=============================
Test r2:0.29
==============================================
== Scaler + anova filter + ridge regression ==
==============================================
----------------------------
-- Parallelize inner loop --
----------------------------
CPU times: user 6.06 s, sys: 836 ms, total: 6.9 s
Wall time: 7.97 s
Test r2:0.86
----------------------------
-- Parallelize outer loop --
----------------------------
CPU times: user 270 ms, sys: 129 ms, total: 399 ms
Wall time: 3.51 s
Test r2:0.86
=====================================
== Scaler + Elastic-net regression ==
=====================================
----------------------------
-- Parallelize outer loop --
----------------------------
CPU times: user 44.4 ms, sys: 80.5 ms, total: 125 ms
Wall time: 1.43 s
Test r2:0.82
-----------------------------------------------
-- Parallelize outer loop + built-in CV --
-- Remark: scaler is only done on outer loop --
-----------------------------------------------
CPU times: user 227 ms, sys: 0 ns, total: 227 ms
Wall time: 225 ms
Test r2:0.82
Now we combine standardization of input features, feature selection and learner with hyper-parameter within a pipeline
which is warped in a grid search procedure to select the best hyperparameters based on a (inner)CV. The overall is
plugged in an outer CV.
import numpy as np
from sklearn import datasets
import sklearn.linear_model as lm
from sklearn import preprocessing
from sklearn.cross_validation import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif
from sklearn.pipeline import Pipeline
from sklearn.grid_search import GridSearchCV
import sklearn.metrics as metrics
# Datasets
n_samples, n_features, noise_sd = 100, 100, 20
X, y = datasets.make_classification(n_samples=n_samples, n_features=n_features,
n_informative=5, random_state=42)
print("=============================")
print("== Basic logistic regression ==")
print("=============================")
print("=======================================================")
print("== Scaler + anova filter + ridge logistic regression ==")
print("=======================================================")
anova_ridge = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('selectkbest', SelectKBest(f_classif)),
('ridge', lm.LogisticRegression(penalty='l2', class_weight='balanced'))
])
param_grid = {'selectkbest__k':np.arange(10, 110, 10),
'ridge__C':[.0001, .001, .01, .1, 1, 10, 100, 1000, 10000]}
print("----------------------------")
print("-- Parallelize outer loop --")
print("----------------------------")
print("========================================")
print("== Scaler + lasso logistic regression ==")
print("========================================")
print("----------------------------")
print("-- Parallelize outer loop --")
print("----------------------------")
lasso = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('lasso', lm.LogisticRegression(penalty='l1', class_weight='balanced')),
])
param_grid = {'lasso__C':Cs}
enet_cv = GridSearchCV(lasso, cv=5, param_grid=param_grid, scoring=balanced_acc)
%time scores = cross_val_score(estimator=enet_cv, X=X, y=y, cv=5,\
scoring=balanced_acc, n_jobs=-1)
print("Test bACC:%.2f" % scores.mean())
print("-----------------------------------------------")
print("-- Parallelize outer loop + built-in CV --")
print("-- Remark: scaler is only done on outer loop --")
print("-----------------------------------------------")
lasso_cv = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('lasso', lm.LogisticRegressionCV(Cs=Cs, scoring=balanced_acc)),
])
print("=============================================")
print("== Scaler + Elasticnet logistic regression ==")
print("=============================================")
print("----------------------------")
print("-- Parallelize outer loop --")
print("----------------------------")
enet = Pipeline([
('standardscaler', preprocessing.StandardScaler()),
('enet', lm.SGDClassifier(loss="log", penalty="elasticnet",
alpha=0.0001, l1_ratio=0.15, class_weight='balanced')),
])
param_grid = {'enet__alpha':alphas,
'enet__l1_ratio':l1_ratio}
=============================
== Basic logistic regression ==
=============================
Test bACC:0.52
=======================================================
== Scaler + anova filter + ridge logistic regression ==
=======================================================
----------------------------
-- Parallelize inner loop --
----------------------------
CPU times: user 3.02 s, sys: 562 ms, total: 3.58 s
Wall time: 4.43 s
Test bACC:0.67
----------------------------
-- Parallelize outer loop --
----------------------------
CPU times: user 59.3 ms, sys: 114 ms, total: 174 ms
Wall time: 1.88 s
Test bACC:0.67
========================================
== Scaler + lasso logistic regression ==
========================================
----------------------------
-- Parallelize outer loop --
----------------------------
CPU times: user 81 ms, sys: 96.7 ms, total: 178 ms
Wall time: 484 ms
Test bACC:0.57
-----------------------------------------------
-- Parallelize outer loop + built-in CV --
-- Remark: scaler is only done on outer loop --
-----------------------------------------------
CPU times: user 575 ms, sys: 3.01 ms, total: 578 ms
Wall time: 327 ms
Test bACC:0.60
=============================================
== Scaler + Elasticnet logistic regression ==
=============================================
----------------------------
-- Parallelize outer loop --
----------------------------
CPU times: user 429 ms, sys: 100 ms, total: 530 ms
Wall time: 979 ms
Test bACC:0.61
FIFTEEN
CASE STUDIES OF ML
Sources:
http://archive.ics.uci.edu/ml/datasets/default+of+credit+card+clients Yeh, I. C., & Lien, C. H. (2009). The compar-
isons of data mining techniques for the predictive accuracy of probability of default of credit card clients. Expert
Systems with Applications, 36(2), 2473-2480.
161
Statistics and Machine Learning in Python, Release 0.1
Read dataset
import pandas as pd
import numpy as np
url = 'https://raw.github.com/neurospin/pystatsml/master/data/default%20of%20credit
%20card%20clients.xls'
df = data.copy()
target = 'default payment next month'
print(df.columns)
Missing data
print(df.isnull().sum())
#ID 0
#LIMIT_BAL 0
#SEX 0
#EDUCATION 468
#MARRIAGE 377
#AGE 0
#PAY_0 0
#PAY_2 0
#PAY_3 0
#PAY_4 0
#PAY_5 0
#PAY_6 0
#BILL_AMT1 0
#BILL_AMT2 0
#BILL_AMT3 0
#BILL_AMT4 0
#BILL_AMT5 0
#BILL_AMT6 0
#PAY_AMT1 0
#PAY_AMT2 0
#PAY_AMT3 0
#PAY_AMT4 0
#PAY_AMT5 0
#PAY_AMT6 0
#default payment next month 0
#dtype: int64
describe_factor(df[target])
{0: 23364, 1: 6636}
Univariate analysis
On this large dataset, we can afford to set aside some test samples. This will also save computation time. However we
will have to do some manual work.
import numpy as np
from sklearn import datasets
import sklearn.svm as svm
from sklearn import preprocessing
from sklearn.cross_validation import cross_val_score, train_test_split
from sklearn.cross_validation import StratifiedKFold
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif
from sklearn.pipeline import Pipeline
from sklearn.grid_search import GridSearchCV
import sklearn.metrics as metrics
print("===============================================")
print("== Put aside half of the samples as test set ==")
print("===============================================")
print("=================================")
print("== Scale trainin and test data ==")
print("=================================")
scaler = preprocessing.StandardScaler()
Xtrs = scaler.fit(Xtr).transform(Xtr)
Xtes = scaler.transform(Xte)
print("=========")
print("== SVM ==")
print("=========")
print("========================")
print("== SVM CV Grid search ==")
print("========================")
Cs = [0.001, .01, .1, 1, 10, 100, 1000]
param_grid = {'C':Cs}
print("-------------------")
print("-- SVM Linear L2 --")
print("-------------------")
print("-------------")
print("-- SVM RBF --")
print("-------------")
print("-------------------")
print("-- SVM Linear L1 --")
print("-------------------")
scores = cross_val_score(estimator=svc_lasso_cv,\
X=Xtrs, y=ytr, cv=2, scoring=balanced_acc)
print("Validation bACC:%.2f" % scores.mean())
#Validation bACC:0.67
SIXTEEN
genindex
modindex
search
167