Nothing Special   »   [go: up one dir, main page]

Download as pdf or txt
Download as pdf or txt
You are on page 1of 64

Full download test bank at ebookmeta.

com

Machine Learning with Python Cookbook 2nd Edition


Chris Albon

For dowload this book click LINK or Button below

https://ebookmeta.com/product/machine-learning-
with-python-cookbook-2nd-edition-chris-albon/

OR CLICK BUTTON

DOWLOAD EBOOK

Download More ebooks from https://ebookmeta.com


More products digital (pdf, epub, mobi) instant
download maybe you interests ...

Machine Learning with Python Cookbook Practical


Solutions from Preprocessing to Deep Learning 2nd Ed
Release 5 2nd Edition Chris Albon

https://ebookmeta.com/product/machine-learning-with-python-
cookbook-practical-solutions-from-preprocessing-to-deep-
learning-2nd-ed-release-5-2nd-edition-chris-albon/

Machine Learning with Python Cookbook, 2nd Edition Kyle


Gallatin

https://ebookmeta.com/product/machine-learning-with-python-
cookbook-2nd-edition-kyle-gallatin/

Machine Learning with Python Cookbook, 2nd Edition


(First Early Release) Kyle Gallatin

https://ebookmeta.com/product/machine-learning-with-python-
cookbook-2nd-edition-first-early-release-kyle-gallatin/

Ensemble Machine Learning With Python: 7-Day Mini-


Course Jason Brownlee

https://ebookmeta.com/product/ensemble-machine-learning-with-
python-7-day-mini-course-jason-brownlee/
Natural Language Processing Recipes: Unlocking Text
Data with Machine Learning and Deep Learning Using
Python 2nd Edition Akshay Kulkarni

https://ebookmeta.com/product/natural-language-processing-
recipes-unlocking-text-data-with-machine-learning-and-deep-
learning-using-python-2nd-edition-akshay-kulkarni-2/

Natural Language Processing Recipes: Unlocking Text


Data with Machine Learning and Deep Learning Using
Python 2nd Edition Akshay Kulkarni

https://ebookmeta.com/product/natural-language-processing-
recipes-unlocking-text-data-with-machine-learning-and-deep-
learning-using-python-2nd-edition-akshay-kulkarni/

Deep Learning with Python 2nd Edition Nikhil Ketkar

https://ebookmeta.com/product/deep-learning-with-python-2nd-
edition-nikhil-ketkar/

Adaptive Machine Learning Algorithms with Python: Solve


Data Analytics and Machine Learning Problems on Edge
Devices 1st Edition Chanchal Chatterjee

https://ebookmeta.com/product/adaptive-machine-learning-
algorithms-with-python-solve-data-analytics-and-machine-learning-
problems-on-edge-devices-1st-edition-chanchal-chatterjee/

Learn TensorFlow 2.0: Implement Machine Learning and


Deep Learning Models with Python 1st Edition Pramod
Singh

https://ebookmeta.com/product/learn-tensorflow-2-0-implement-
machine-learning-and-deep-learning-models-with-python-1st-
edition-pramod-singh/
Machine Learning with Python Cookbook
SECOND EDITION
Practical Solutions from Preprocessing to Deep Learning

With Early Release ebooks, you get books in their earliest form—the author’s raw and unedited content as they write—so you can
take advantage of these technologies long before the official release of these titles.

Kyle Gallatin and Chris Albon


Machine Learning with Python Cookbook
by Kyle Gallatin and Chris Albon
Copyright © 2023 Kyle Gallatin. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions
are also available for most titles (http://oreilly.com). For more information, contact our
corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com.
Acquisitions Editor: Nicole Butterfield
Development Editor Jeff Bleiel
Production Editor: Christopher Faucher
Interior Designer: David Futato
Cover Designer: Karen Montgomery
April 2018: First Edition
October 2023: Second Edition
Revision History for the Early Release
2022-08-24: First Release
2022-10-05: Second Release
2022-12-08: Third Release
2023-01-18: Fourth Release
See http://oreilly.com/catalog/errata.csp?isbn=9781098135720 for release details.
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Machine Learning with Python
Cookbook, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.
The views expressed in this work are those of the authors and do not represent the publisher’s views.
While the publisher and the authors have used good faith efforts to ensure that the information and
instructions contained in this work are accurate, the publisher and the authors disclaim all responsibility
for errors or omissions, including without limitation responsibility for damages resulting from the use of
or reliance on this work. Use of the information and instructions contained in this work is at your own
risk. If any code samples or other technology this work contains or describes is subject to open source
licenses or the intellectual property rights of others, it is your responsibility to ensure that your use
thereof complies with such licenses and/or rights.
978-1-098-13566-9
Chapter 1. Working with Vectors, Matrices
and Arrays in NumPy

A NOTE FOR EARLY RELEASE READERS


With Early Release ebooks, you get books in their earliest form—the authors’ raw and unedited content as they write—so you can
take advantage of these technologies long before the official release of these titles.
This will be the 1st chapter of the final book.
If you have comments about how we might improve the content and/or examples in this book, or if you notice missing material
within this chapter, please reach out to the authors at feedback.mlpythoncookbook@gmail.com.

1.0 Introduction
NumPy is a foundational tool of the Python machine learning stack. NumPy allows for efficient
operations on the data structures often used in machine learning: vectors, matrices, and tensors. While
NumPy is not the focus of this book, it will show up frequently throughout the following chapters. This
chapter covers the most common NumPy operations we are likely to run into while working on machine
learning workflows.

1.1 Creating a Vector

Problem
You need to create a vector.

Solution
Use NumPy to create a one-dimensional array:

# Load library
import numpy as np

# Create a vector as a row


vector_row = np.array([1, 2, 3])

# Create a vector as a column


vector_column = np.array([[1],
[2],
[3]])

Discussion
NumPy’s main data structure is the multidimensional array. A vector is just an array with a single
dimension. In order to create a vector, we simply create a one-dimensional array. Just like vectors, these
arrays can be represented horizontally (i.e., rows) or vertically (i.e., columns).

See Also
Vectors, Math Is Fun
Euclidean vector, Wikipedia

1.2 Creating a Matrix

Problem
You need to create a matrix.

Solution
Use NumPy to create a two-dimensional array:

# Load library
import numpy as np

# Create a matrix
matrix = np.array([[1, 2],
[1, 2],
[1, 2]])

Discussion
To create a matrix we can use a NumPy two-dimensional array. In our solution, the matrix contains
three rows and two columns (a column of 1s and a column of 2s).
NumPy actually has a dedicated matrix data structure:

matrix_object = np.mat([[1, 2],


[1, 2],
[1, 2]])

matrix([[1, 2],
[1, 2],
[1, 2]])

However, the matrix data structure is not recommended for two reasons. First, arrays are the de facto
standard data structure of NumPy. Second, the vast majority of NumPy operations return arrays, not
matrix objects.

See Also
Matrix, Wikipedia
Matrix, Wolfram MathWorld
1.3 Creating a Sparse Matrix

Problem
Given data with very few nonzero values, you want to efficiently represent it.

Solution
Create a sparse matrix:

# Load libraries
import numpy as np
from scipy import sparse

# Create a matrix
matrix = np.array([[0, 0],
[0, 1],
[3, 0]])

# Create compressed sparse row (CSR) matrix


matrix_sparse = sparse.csr_matrix(matrix)

Discussion
A frequent situation in machine learning is having a huge amount of data; however, most of the
elements in the data are zeros. For example, imagine a matrix where the columns are every movie on
Netflix, the rows are every Netflix user, and the values are how many times a user has watched that
particular movie. This matrix would have tens of thousands of columns and millions of rows! However,
since most users do not watch most movies, the vast majority of elements would be zero.
A sparse matrix is a matrix in which most elements are 0. Sparse matrices only store nonzero elements
and assume all other values will be zero, leading to significant computational savings. In our solution,
we created a NumPy array with two nonzero values, then converted it into a sparse matrix. If we view
the sparse matrix we can see that only the nonzero values are stored:

# View sparse matrix


print(matrix_sparse)

(1, 1) 1
(2, 0) 3

There are a number of types of sparse matrices. However, in compressed sparse row (CSR) matrices,
(1, 1) and (2, 0) represent the (zero-indexed) indices of the non-zero values 1 and 3, respectively.
For example, the element 1 is in the second row and second column. We can see the advantage of sparse
matrices if we create a much larger matrix with many more zero elements and then compare this larger
matrix with our original sparse matrix:

# Create larger matrix


matrix_large = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

# Create compressed sparse row (CSR) matrix


matrix_large_sparse = sparse.csr_matrix(matrix_large)

# View original sparse matrix


print(matrix_sparse)

(1, 1) 1
(2, 0) 3

# View larger sparse matrix


print(matrix_large_sparse)

(1, 1) 1
(2, 0) 3

As we can see, despite the fact that we added many more zero elements in the larger matrix, its sparse
representation is exactly the same as our original sparse matrix. That is, the addition of zero elements
did not change the size of the sparse matrix.
As mentioned, there are many different types of sparse matrices, such as compressed sparse column, list
of lists, and dictionary of keys. While an explanation of the different types and their implications is
outside the scope of this book, it is worth noting that while there is no “best” sparse matrix type, there
are meaningful differences between them and we should be conscious about why we are choosing one
type over another.

See Also
Sparse matrices, SciPy documentation
101 Ways to Store a Sparse Matrix

1.4 Pre-allocating Numpy Arrays

Problem
You need to pre-allocate arrays of a given size with some value.

Solution
NumPy has functions for generating vectors and matrices of any size using 0s, 1s, or values of your
choice.

# Load library
import numpy as np

# Generate a vector of shape (1,5) containing all zeros


vector = np.zeros(shape=5)

# View the vector


print(vector)

array([0., 0., 0., 0., 0.])


# Generate a matrix of shape (3,3) containing all ones
matrix = np.full(shape=(3,3), fill_value=1)

# View the vector


print(matrix)

array([[1., 1., 1.],


[1., 1., 1.],
[1., 1., 1.]])

Discussion
Generating arrays prefilled with data is useful for a number of purposes, such as making code more
performant or having synthetic data to test algorithms with. In many programming languages, pre-
allocating an array of default values (such as 0s) is considered common practice.

1.5 Selecting Elements

Problem
You need to select one or more elements in a vector or matrix.

Solution
NumPy’s arrays make it easy to select elements in vectors or matrices:

# Load library
import numpy as np
# Create row vector
vector = np.array([1, 2, 3, 4, 5, 6])

# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Select third element of vector


vector[2]

# Select second row, second column


matrix[1,1]

Discussion
Like most things in Python, NumPy arrays are zero-indexed, meaning that the index of the first element
is 0, not 1. With that caveat, NumPy offers a wide variety of methods for selecting (i.e., indexing and
slicing) elements or groups of elements in arrays:

# Select all elements of a vector


vector[:]
array([1, 2, 3, 4, 5, 6])

# Select everything up to and including the third element


vector[:3]

array([1, 2, 3])

# Select everything after the third element


vector[3:]

array([4, 5, 6])

# Select the last element


vector[-1]

# Reverse the vector


vector[::-1]

array([6, 5, 4, 3, 2, 1])

# Select the first two rows and all columns of a matrix


matrix[:2,:]

array([[1, 2, 3],
[4, 5, 6]])

# Select all rows and the second column


matrix[:,1:2]

array([[2],
[5],
[8]])

1.6 Describing a Matrix

Problem
You want to describe the shape, size, and dimensions of the matrix.

Solution
Use the shape, size, and ndim attributes of a NumPy object:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])

# View number of rows and columns


matrix.shape
(3, 4)

# View number of elements (rows * columns)


matrix.size

12

# View number of dimensions


matrix.ndim

Discussion
This might seem basic (and it is); however, time and again it will be valuable to check the shape and
size of an array both for further calculations and simply as a gut check after some operation.

1.7 Applying Functions Over Each Element

Problem
You want to apply some function to all elements in an array.

Solution
Use NumPy’s vectorize method:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Create function that adds 100 to something


add_100 = lambda i: i + 100

# Create vectorized function


vectorized_add_100 = np.vectorize(add_100)
# Apply function to all elements in matrix
vectorized_add_100(matrix)

array([[101, 102, 103],


[104, 105, 106],
[107, 108, 109]])

Discussion
NumPy’s vectorize class converts a function into a function that can apply to all elements in an array
or slice of an array. It’s worth noting that vectorize is essentially a for loop over the elements and
does not increase performance. Furthermore, NumPy arrays allow us to perform operations between
arrays even if their dimensions are not the same (a process called broadcasting). For example, we can
create a much simpler version of our solution using broadcasting:

# Add 100 to all elements


matrix + 100

array([[101, 102, 103],


[104, 105, 106],
[107, 108, 109]])

Broadcasting does not work for all shapes and situations, but a common way of applying simple
operations over all elements of a numpy array.

1.8 Finding the Maximum and Minimum Values

Problem
You need to find the maximum or minimum value in an array.

Solution
Use NumPy’s max and min methods:

# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Return maximum element
np.max(matrix)

# Return minimum element


np.min(matrix)

Discussion
Often we want to know the maximum and minimum value in an array or subset of an array. This can be
accomplished with the max and min methods. Using the axis parameter we can also apply the operation
along a certain axis:

# Find maximum element in each column


np.max(matrix, axis=0)

array([7, 8, 9])

# Find maximum element in each row


np.max(matrix, axis=1)
array([3, 6, 9])

1.9 Calculating the Average, Variance, and Standard Deviation

Problem
You want to calculate some descriptive statistics about an array.

Solution
Use NumPy’s mean, var, and std:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Return mean
np.mean(matrix)

5.0

# Return variance
np.var(matrix)

6.666666666666667

# Return standard deviation


np.std(matrix)

2.5819888974716112

Discussion
Just like with max and min, we can easily get descriptive statistics about the whole matrix or do
calculations along a single axis:

# Find the mean value in each column


np.mean(matrix, axis=0)

array([ 4., 5., 6.])

1.10 Reshaping Arrays

Problem
You want to change the shape (number of rows and columns) of an array without changing the element
values.
Solution
Use NumPy’s reshape:

# Load library
import numpy as np

# Create 4x3 matrix


matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]])
# Reshape matrix into 2x6 matrix
matrix.reshape(2, 6)

array([[ 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12]])

Discussion
reshape allows us to restructure an array so that we maintain the same data but it is organized as a
different number of rows and columns. The only requirement is that the shape of the original and new
matrix contain the same number of elements (i.e., the same size). We can see the size of a matrix using
size:

matrix.size

12

One useful argument in reshape is -1, which effectively means “as many as needed,” so reshape(1,
-1) means one row and as many columns as needed:

matrix.reshape(1, -1)

array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]])

Finally, if we provide one integer, reshape will return a 1D array of that length:

matrix.reshape(12)

array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])

1.11 Transposing a Vector or Matrix

Problem
You need to transpose a vector or matrix.

Solution
Use the T method:
# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Transpose matrix
matrix.T

array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])

Discussion
Transposing is a common operation in linear algebra where the column and row indices of each element
are swapped. One nuanced point that is typically overlooked outside of a linear algebra class is that,
technically, a vector cannot be transposed because it is just a collection of values:

# Transpose vector
np.array([1, 2, 3, 4, 5, 6]).T

array([1, 2, 3, 4, 5, 6])

However, it is common to refer to transposing a vector as converting a row vector to a column vector
(notice the second pair of brackets) or vice versa:

# Tranpose row vector


np.array([[1, 2, 3, 4, 5, 6]]).T

array([[1],
[2],
[3],
[4],
[5],
[6]])

1.12 Flattening a Matrix

Problem
You need to transform a matrix into a one-dimensional array.

Solution
Use flatten:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Flatten matrix
matrix.flatten()

array([1, 2, 3, 4, 5, 6, 7, 8, 9])

Discussion
flatten is a simple method to transform a matrix into a one-dimensional array. Alternatively, we can
use reshape to create a row vector:

matrix.reshape(1, -1)

array([[1, 2, 3, 4, 5, 6, 7, 8, 9]])

One more common method to flatten arrays is the ravel method. Unlike flatten which returns a copy
of the original array, ravel operates on the original object itself and is therefore slightly faster. It also
lets us flatten lists of arrays, which we can’t do with the flatten method. This operation is useful for
flattening very large arrays and speeding up code.

# Create one matrix


matrix_a = np.array([[1, 2],
[3, 4]])

# Create a second matrix


matrix_b = np.array([[5, 6],
[7, 8]])

# Create a list of matrices


matrix_list = [matrix_a, matrix_b]
# Flatten the entire list of matrices
np.ravel(matrix_list)

array([1, 2, 3, 4, 5, 6, 7, 8])

1.13 Finding the Rank of a Matrix

Problem
You need to know the rank of a matrix.

Solution
Use NumPy’s linear algebra method matrix_rank:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 1, 1],
[1, 1, 10],
[1, 1, 15]])
# Return matrix rank
np.linalg.matrix_rank(matrix)

Discussion
The rank of a matrix is the dimensions of the vector space spanned by its columns or rows. Finding the
rank of a matrix is easy in NumPy thanks to matrix_rank.

See Also
The Rank of a Matrix, CliffsNotes

1.14 Getting the Diagonal of a Matrix

Problem
You need to get the diagonal elements of a matrix.

Solution
Use diagonal:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 2, 3],
[2, 4, 6],
[3, 8, 9]])
# Return diagonal elements
matrix.diagonal()

array([1, 4, 9])

Discussion
NumPy makes getting the diagonal elements of a matrix easy with diagonal. It is also possible to get a
diagonal off from the main diagonal by using the offset parameter:

# Return diagonal one above the main diagonal


matrix.diagonal(offset=1)

array([2, 6])

# Return diagonal one below the main diagonal


matrix.diagonal(offset=-1)

array([2, 8])
1.15 Calculating the Trace of a Matrix

Problem
You need to calculate the trace of a matrix.

Solution
Use trace:

# Load library
import numpy as np
# Create matrix
matrix = np.array([[1, 2, 3],
[2, 4, 6],
[3, 8, 9]])
# Return trace
matrix.trace()

14

Discussion
The trace of a matrix is the sum of the diagonal elements and is often used under the hood in machine
learning methods. Given a NumPy multidimensional array, we can calculate the trace using trace. We
can also return the diagonal of a matrix and calculate its sum:

# Return diagonal and sum elements


sum(matrix.diagonal())

14

See Also
The Trace of a Square Matrix

1.16 Calculating Dot Products

Problem
You need to calculate the dot product of two vectors.

Solution
Use NumPy’s dot:

# Load library
import numpy as np
# Create two vectors
vector_a = np.array([1,2,3])
vector_b = np.array([4,5,6])

# Calculate dot product


np.dot(vector_a, vector_b)

32

Discussion
The dot product of two vectors, a and b, is defined as:

where ai is the ith element of vector a and bi is the ith element of vector b. We can use NumPy’s dot
function to calculate the dot product. Alternatively, in Python 3.5+ we can use the new @ operator:

# Calculate dot product


vector_a @ vector_b

32

See Also
Vector dot product and vector length, Khan Academy
Dot Product, Paul’s Online Math Notes

1.17 Adding and Subtracting Matrices

Problem
You want to add or subtract two matrices.

Solution
Use NumPy’s add and subtract:

# Load library
import numpy as np

# Create matrix
matrix_a = np.array([[1, 1, 1],
[1, 1, 1],
[1, 1, 2]])

# Create matrix
matrix_b = np.array([[1, 3, 1],
[1, 3, 1],
[1, 3, 8]])

# Add two matrices


np.add(matrix_a, matrix_b)

array([[ 2, 4, 2],
[ 2, 4, 2],
[ 2, 4, 10]])

# Subtract two matrices


np.subtract(matrix_a, matrix_b)

array([[ 0, -2, 0],


[ 0, -2, 0],
[ 0, -2, -6]])

Discussion
Alternatively, we can simply use the + and - operators:

# Add two matrices


matrix_a + matrix_b

array([[ 2, 4, 2],
[ 2, 4, 2],
[ 2, 4, 10]])

1.18 Multiplying Matrices

Problem
You want to multiply two matrices.

Solution
Use NumPy’s dot:

# Load library
import numpy as np

# Create matrix
matrix_a = np.array([[1, 1],
[1, 2]])
# Create matrix
matrix_b = np.array([[1, 3],
[1, 2]])

# Multiply two matrices


np.dot(matrix_a, matrix_b)

array([[2, 5],
[3, 7]])

Discussion
Alternatively, in Python 3.5+ we can use the @ operator:

# Multiply two matrices


matrix_a @ matrix_b

array([[2, 5],
[3, 7]])

If we want to do element-wise multiplication, we can use the * operator:

# Multiply two matrices element-wise


matrix_a * matrix_b

array([[1, 3],
[1, 4]])

See Also
Array vs. Matrix Operations, MathWorks

1.19 Inverting a Matrix

Problem
You want to calculate the inverse of a square matrix.

Solution
Use NumPy’s linear algebra inv method:

# Load library
import numpy as np

# Create matrix
matrix = np.array([[1, 4],
[2, 5]])
# Calculate inverse of matrix
np.linalg.inv(matrix)

array([[-1.66666667, 1.33333333],
[ 0.66666667, -0.33333333]])

Discussion
The inverse of a square matrix, A, is a second matrix A–1, such that:

where I is the identity matrix. In NumPy we can use linalg.inv to calculate A–1 if it exists. To see this
in action, we can multiply a matrix by its inverse and the result is the identity matrix:

# Multiply matrix and its inverse


matrix @ np.linalg.inv(matrix)

array([[ 1., 0.],


[ 0., 1.]])

See Also
Inverse of a Matrix

1.20 Generating Random Values

Problem
You want to generate pseudorandom values.

Solution
Use NumPy’s random:

# Load library
import numpy as np

# Set seed
np.random.seed(0)

# Generate three random floats between 0.0 and 1.0


np.random.random(3)

array([ 0.5488135 , 0.71518937, 0.60276338])

Discussion
NumPy offers a wide variety of means to generate random numbers, many more than can be covered
here. In our solution we generated floats; however, it is also common to generate integers:

# Generate three random integers between 0 and 10


np.random.randint(0, 11, 3)

array([3, 7, 9])

Alternatively, we can generate numbers by drawing them from a distribution (note this is not technically
random):

# Draw three numbers from a normal distribution with mean 0.0


# and standard deviation of 1.0
np.random.normal(0.0, 1.0, 3)

array([-1.42232584, 1.52006949, -0.29139398])

# Draw three numbers from a logistic distribution with mean 0.0 and scale of 1.0
np.random.logistic(0.0, 1.0, 3)

array([-0.98118713, -0.08939902, 1.46416405])

# Draw three numbers greater than or equal to 1.0 and less than 2.0
np.random.uniform(1.0, 2.0, 3)
array([ 1.47997717, 1.3927848 , 1.83607876])

Finally, it can sometimes be useful to return the same random numbers multiple times to get predictable,
repeatable results. We can do this by setting the “seed” (an integer) of the pseudorandom generator.
Random processes with the same seed will always produce the same output. We will use seeds
throughout this book so that the code you see in the book and the code you run on your computer
produces the same results.
Chapter 2. Loading Data

A NOTE FOR EARLY RELEASE READERS


With Early Release ebooks, you get books in their earliest form—the authors’ raw and unedited content as they write—so you can
take advantage of these technologies long before the official release of these titles.
This will be the 2nd chapter of the final book.
If you have comments about how we might improve the content and/or examples in this book, or if you notice missing material
within this chapter, please reach out to the authors at feedback.mlpythoncookbook@gmail.com.

2.0 Introduction
The first step in any machine learning endeavor is to get the raw data into our system. The raw data
might be a logfile, dataset file, database, or cloud blob store such as Amazon S3. Furthermore, often we
will want to retrieve data from multiple sources.
The recipes in this chapter look at methods of loading data from a variety of sources, including CSV
files and SQL databases. We also cover methods of generating simulated data with desirable properties
for experimentation. Finally, while there are many ways to load data in the Python ecosystem, we will
focus on using the pandas library’s extensive set of methods for loading external data, and using scikit-
learn—​an open source machine learning library in Python—​for generating simulated data.

2.1 Loading a Sample Dataset

Problem
You want to load a preexisting sample dataset from the scikit-learn library.

Solution
scikit-learn comes with a number of popular datasets for you to use:

# Load scikit-learn's datasets


from sklearn import datasets

# Load digits dataset


digits = datasets.load_digits()
# Create features matrix
features = digits.data

# Create target vector


target = digits.target

# View first observation


features[0]
array([ 0., 0., 5., 13., 9., 1., 0., 0., 0., 0., 13.,
15., 10., 15., 5., 0., 0., 3., 15., 2., 0., 11.,
8., 0., 0., 4., 12., 0., 0., 8., 8., 0., 0.,
5., 8., 0., 0., 9., 8., 0., 0., 4., 11., 0.,
1., 12., 7., 0., 0., 2., 14., 5., 10., 12., 0.,
0., 0., 0., 6., 13., 10., 0., 0., 0.])

Discussion
Often we do not want to go through the work of loading, transforming, and cleaning a real-world dataset
before we can explore some machine learning algorithm or method. Luckily, scikit-learn comes with
some common datasets we can quickly load. These datasets are often called “toy” datasets because they
are far smaller and cleaner than a dataset we would see in the real world. Some popular sample datasets
in scikit-learn are:
load_iris
Contains 150 observations on the measurements of Iris flowers. It is a good dataset for exploring
classification algorithms.
load_digits
Contains 1,797 observations from images of handwritten digits. It is a good dataset for teaching
image classification.
To see more details on any of the datasets above, you can print the DESCR attribute:

# Load scikit-learn's datasets


from sklearn import datasets

# Load digits dataset


digits = datasets.load_digits()

# Print the attribute


print(digits.DESCR)

.. _digits_dataset:

Optical recognition of handwritten digits dataset


--------------------------------------------------

**Data Set Characteristics:**

:Number of Instances: 1797


:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range 0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
...

See Also
scikit-learn toy datasets
The Digit Dataset

2.2 Creating a Simulated Dataset


Problem
You need to generate a dataset of simulated data.

Solution
scikit-learn offers many methods for creating simulated data. Of those, three methods are particularly
useful: make_regression, make_classification, and make_blobs.
When we want a dataset designed to be used with linear regression, make_regression is a good choice:

# Load library
from sklearn.datasets import make_regression

# Generate features matrix, target vector, and the true coefficients


features, target, coefficients = make_regression(n_samples = 100,
n_features = 3,
n_informative = 3,
n_targets = 1,
noise = 0.0,
coef = True,
random_state = 1)

# View feature matrix and target vector


print('Feature Matrix\n', features[:3])
print('Target Vector\n', target[:3])

Feature Matrix
[[ 1.29322588 -0.61736206 -0.11044703]
[-2.793085 0.36633201 1.93752881]
[ 0.80186103 -0.18656977 0.0465673 ]]
Target Vector
[-10.37865986 25.5124503 19.67705609]

If we are interested in creating a simulated dataset for classification, we can use


make_classification:

# Load library
from sklearn.datasets import make_classification

# Generate features matrix and target vector


features, target = make_classification(n_samples = 100,
n_features = 3,
n_informative = 3,
n_redundant = 0,
n_classes = 2,
weights = [.25, .75],
random_state = 1)

# View feature matrix and target vector


print('Feature Matrix\n', features[:3])
print('Target Vector\n', target[:3])

Feature Matrix
[[ 1.06354768 -1.42632219 1.02163151]
[ 0.23156977 1.49535261 0.33251578]
[ 0.15972951 0.83533515 -0.40869554]]
Target Vector
[1 0 0]

Finally, if we want a dataset designed to work well with clustering techniques, scikit-learn offers
make_blobs:
# Load library
from sklearn.datasets import make_blobs

# Generate feature matrix and target vector


features, target = make_blobs(n_samples = 100,
n_features = 2,
centers = 3,
cluster_std = 0.5,
shuffle = True,
random_state = 1)
# View feature matrix and target vector
print('Feature Matrix\n', features[:3])
print('Target Vector\n', target[:3])

Feature Matrix
[[ -1.22685609 3.25572052]
[ -9.57463218 -4.38310652]
[-10.71976941 -4.20558148]]
Target Vector
[0 1 1]

Discussion
As might be apparent from the solutions, make_regression returns a feature matrix of float values and
a target vector of float values, while make_classification and make_blobs return a feature matrix of
float values and a target vector of integers representing membership in a class.
scikit-learn’s simulated datasets offer extensive options to control the type of data generated. scikit-
learn’s documentation contains a full description of all the parameters, but a few are worth noting.
In make_regression and make_classification, n_informative determines the number of features
that are used to generate the target vector. If n_informative is less than the total number of features
(n_features), the resulting dataset will have redundant features that can be identified through feature
selection techniques.
In addition, make_classification contains a weights parameter that allows us to simulate datasets
with imbalanced classes. For example, weights = [.25, .75] would return a dataset with 25% of
observations belonging to one class and 75% of observations belonging to a second class.
For make_blobs, the centers parameter determines the number of clusters generated. Using the
matplotlib visualization library, we can visualize the clusters generated by make_blobs:

# Load library
import matplotlib.pyplot as plt

# View scatterplot
plt.scatter(features[:,0], features[:,1], c=target)
plt.show()
See Also
make_regression documentation
make_classification documentation
make_blobs documentation

2.3 Loading a CSV File

Problem
You need to import a comma-separated values (CSV) file.

Solution
Use the pandas library’s read_csv to load a local or hosted CSV file into a Pandas DataFrame:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/data.csv'

# Load dataset
dataframe = pd.read_csv(url)

# View first two rows


dataframe.head(2)

integer datetime category


0 5 2015-01-01 00:00:00 0
1 5 2015-01-01 00:00:01 0

Discussion
There are two things to note about loading CSV files. First, it is often useful to take a quick look at the
contents of the file before loading. It can be very helpful to see how a dataset is structured beforehand
and what parameters we need to set to load in the file. Second, read_csv has over 30 parameters and
therefore the documentation can be daunting. Fortunately, those parameters are mostly there to allow it
to handle a wide variety of CSV formats.
CSV files get their names from the fact that the values are literally separated by commas (e.g., one row
might be 2,"2015-01-01 00:00:00",0); however, it is common for “CSV” files to use other (termed
“TSVs”). pandas’ sep parameter allows us to define the delimiter used in the file. Although it is not
always the case, a common formatting issue with CSV files is that the first line of the file is used to
define column headers (e.g., integer, datetime, category in our solution). The header parameter
allows us to specify if or where a header row exists. If a header row does not exist, we set
header=None.
The read_csv function returns a Pandas DataFrame: a common and useful object for working with
tabular data that we’ll cover in more depth throughout this book.

2.4 Loading an Excel File

Problem
You need to import an Excel spreadsheet.

Solution
Use the pandas library’s read_excel to load an Excel spreadsheet:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/data.xlsx'

# Load data
dataframe = pd.read_excel(url, sheet_name=0, header=1)

# View the first two rows


dataframe.head(2)

5 2015-01-01 00:00:00 0
0 5 2015-01-01 00:00:01 0
1 9 2015-01-01 00:00:02 0

Discussion
This solution is similar to our solution for reading CSV files. The main difference is the additional
parameter, sheetname, that specifies which sheet in the Excel file we wish to load. sheetname can
accept both strings containing the name of the sheet and integers pointing to sheet positions (zero-
indexed). If we need to load multiple sheets, include them as a list. For example, sheetname=[0,1,2,
"Monthly Sales"] will return a dictionary of pandas DataFrames containing the first, second, and third
sheets and the sheet named Monthly Sales.

2.5 Loading a JSON File

Problem
You need to load a JSON file for data preprocessing.

Solution
The pandas library provides read_json to convert a JSON file into a pandas object:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/data.json'
# Load data
dataframe = pd.read_json(url, orient='columns')

# View the first two rows


dataframe.head(2)

category datetime integer


0 0 2015-01-01 00:00:00 5
1 0 2015-01-01 00:00:01 5

Discussion
Importing JSON files into pandas is similar to the last few recipes we have seen. The key difference is
the orient parameter, which indicates to pandas how the JSON file is structured. However, it might
take some experimenting to figure out which argument (split, records, index, columns, and values)
is the right one. Another helpful tool pandas offers is json_normalize, which can help convert
semistructured JSON data into a pandas DataFrame.

See Also
json_normalize documentation

2.6 Loading a parquet file

Problem
You need to load a parquet file.

Solution
The pandas read_parquet function allows us to read in parquet files:

# Load library
import pandas as pd

# Create URL
url = 'https://machine-learning-python-cookbook.s3.amazonaws.com/data.parquet'

# Load data
dataframe = pd.read_parquet(url)

# View the first two rows


dataframe.head(2)

category datetime integer


0 0 2015-01-01 00:00:00 5
1 0 2015-01-01 00:00:01 5

Discussion
Paruqet is a popular data storage format in the large data space. It is often used with big data tools such
as hadoop and spark. While Pyspark is outside the focus of this book, it’s highly likely companies
operating a large scale will use an efficient data storage format such as parquet and it’s valuable to know
how to read it into a dataframe and manipulate it.

See Also
Apache Parquet Documentation

2.7 Loading a avro file

Problem
You need to load an avro file into a pandas dataframe.

Solution
The use the pandavro library’s read_avro method:

# Load library
import pandavro as pdx

# Create URL
url = 'https://machine-learning-python-cookbook.s3.amazonaws.com/data.avro'

# Load data
dataframe = pdx.read_avro(url)

# View the first two rows


dataframe.head(2)

category datetime integer


0 0 2015-01-01 00:00:00 5
1 0 2015-01-01 00:00:01 5

Discussion
Apache Avro is an open source, binary data format that relies on schemas for the data structure. At the
time of writing it is not as common as parquet. However, large binary data formats such as avro, thrift
and protocol buffers are growing in popularity due to the efficient nature of these formats. If you work
with large data systems, you’re likely to run into one of these formats (such as avro) in the near future.

See Also
Apache Avro Docs

2.8 Loading a TFRecord file

Problem
You need to load a TFRecord file into a pandas dataframe.

Solution

category datetime integer


0 0 2015-01-01 00:00:00 5
1 0 2015-01-01 00:00:01 5

Discussion
Like avro, TFRecord is a binary data format (in this case it is based on protocol buffers) - however it is
specific to TensorFlow.

See Also
TFRecord Docs

2.9 Querying a SQLite Database

Problem
You need to load data from a database using the structured query language (SQL).

Solution
pandas’ read_sql_query allows us to make a SQL query to a database and load it:
# Load libraries
import pandas as pd
from sqlalchemy import create_engine

# Create a connection to the database


database_connection = create_engine('sqlite:///sample.db')

# Load data
dataframe = pd.read_sql_query('SELECT * FROM data', database_connection)
# View first two rows
dataframe.head(2)

first_name last_name age preTestScore postTestScore


0 Jason Miller 42 4 25
1 Molly Jacobson 52 24 94

Discussion
SQL is the lingua franca for pulling data from databases. In this recipe we first use create_engine to
define a connection to a SQL database engine called SQLite. Next we use pandas’ read_sql_query to
query that database using SQL and put the results in a DataFrame.
SQL is a language in its own right and, while beyond the scope of this book, it is certainly worth
knowing for anyone wanting to learn machine learning. Our SQL query, SELECT * FROM data, asks the
database to give us all columns (*) from the table called data.
Note that this is one of a few recipes in this book that will not run without extra code. Specifically,
create_engine('sqlite:///sample.db') assumes that an SQLite database already exists.

See Also
SQLite
W3Schools SQL Tutorial

2.10 Querying a Remote SQL Database

Problem
You need to connect to, and read from, a remote SQL database.

Solution
Create a connection with pymysql and read it into a dataframe with pandas:

# Import libraries
import pymysql
import pandas as pd

# Create a DB connection
# Use the example below to start a DB instance
# https://github.com/kylegallatin/mysql-db-example
conn = pymysql.connect(
host='localhost',
user='root',
password = "",
db='db',
)

# Read the SQL query into a dataframe


dataframe = pd.read_sql("select * from data", conn)
# View the first 2 rows
dataframe.head(2)

integer datetime category


0 5 2015-01-01 00:00:00 0
1 5 2015-01-01 00:00:01 0

Discussion
Out of all of the recipes presented in this chapter, this recipe is probably the one we will use most in the
real world. While connecting and reading from an example sqlite database is useful, it’s likely not
representative of tables you’ll need to connect to in the an enterprise environment. Most SQL instances
that you’ll connect to will require you to connect to the host and port of a remote machine, specifying a
username and password for authentication. This example requires you to start a running SQL instance
locally that mimics a remote server (the host 127.0.0.1 is actually your localhost) so that you can get a
sense for the workflow.

See Also
Pymysql Documentation
Pandas Read SQL

2.11 Loading Data from a Google Sheet

Problem
You need to read data in directly from a Google Sheet.

Solution
Use Pandas read CSV and a URL that exports the Google Sheet as a CSV:

# Import libraries
import pandas as pd

# Google Sheet URL that downloads the sheet as a CSV


url = "https://docs.google.com/spreadsheets/d/1ehC-9otcAuitqnmWksqt1mOrTRCL38dv0K9UjhwzTOA/export?
format=csv"

# Read the CSV into a dataframe


dataframe = pd.read_csv(url)

# View the first 2 rows


dataframe.head(2)

integer datetime category


0 5 2015-01-01 00:00:00 0
1 5 2015-01-01 00:00:01 0

Discussion
While Google Sheets can also easily be downloaded, it’s sometimes helpful to be able to read them
directly into Python without any intermediate steps. The /export?format=csv query parameter at the
end of the URL above creates an endpoint from which we can either download the file or read it directly
into pandas.

See Also
Google Sheets API

2.12 Loading Data from an S3 Bucket

Problem
You need to read a CSV file from an S3 bucket you have access to.

Solution
Add storage options to pandas giving it access to the S3 object:

# Import libraries
import pandas as pd

# S3 path to csv
s3_uri = "s3://machine-learning-python-cookbook/data.csv"

# Set AWS credentails (replace with your own)


ACCESS_KEY_ID = "xxxxxxxxxxxxx"
SECRET_ACCESS_KEY = "xxxxxxxxxxxxxxxx"

# Read the csv into a dataframe


dataframe = pd.read_csv(s3_uri,storage_options={
"key": ACCESS_KEY_ID,
"secret": SECRET_ACCESS_KEY,
}
)

# View first two rows


dataframe.head(2)

integer datetime category


0 5 2015-01-01 00:00:00 0
1 5 2015-01-01 00:00:01 0
Discussion
In the modern day, many enterprises keep data in cloud provider blob stores such as Amazon S3 or
Google Cloud Storage (GCS). It’s common for machine learning practitioners to connect to these
sources in order to retrieve data. Although the S3 URI above (s3://machine-learning-python-
cookbook/data.csv) is public, it still requires you to provide your own AWS access credentials in
order to access it. It’s worth noting that public objects also have http urls from which they can download
files such as this one for the CSV file above.

See Also
Amazon S3
Setting up AWS access credentials

2.13 Loading Unstructured Data

Problem
You need to load in unstructured data like text or images.

Solution
Use the base Python open function to load the information:

# import libraries
import requests
# URL to download the txt file from
txt_url = "https://machine-learning-python-cookbook.s3.amazonaws.com/text.txt"

# Get the txt file


r = requests.get(txt_url)
# Write it to text.txt locally
with open('text.txt', 'wb') as f:
f.write(r.content)

# Read in the file


with open('text.txt', 'r') as f:
text = f.read()
# Print the content
print(text)

Hello there!

Discussion
While structured data can easily be read in from CSV, JSON, or various databases, unstructured data
can be more challegning and may require custom processing down the line. Sometimes, it’s helpful to
open and read in files using Python’s basic open function. This allows us to open files, and then read the
content of that file.
See Also
Python’s open function
Context managers in Python
Chapter 3. Data Wrangling

A NOTE FOR EARLY RELEASE READERS


With Early Release ebooks, you get books in their earliest form—the authors’ raw and unedited content as they write—so you can
take advantage of these technologies long before the official release of these titles.
This will be the 3rd chapter of the final book.
If you have comments about how we might improve the content and/or examples in this book, or if you notice missing material
within this chapter, please reach out to the authors at feedback.mlpythoncookbook@gmail.com.

3.0 Introduction
Data wrangling is a broad term used, often informally, to describe the process of transforming raw data
to a clean and organized format ready for use. For us, data wrangling is only one step in preprocessing
our data, but it is an important step.
The most common data structure used to “wrangle” data is the data frame, which can be both intuitive
and incredibly versatile. Data frames are tabular, meaning that they are based on rows and columns like
you would see in a spreadsheet. Here is a data frame created from data about passengers on the Titanic:

# Load library
import pandas as pd
# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data as a dataframe


dataframe = pd.read_csv(url)
# Show first 5 rows
dataframe.head(5)

Name PClass Age Sex Survived SexCode


0 Allen, Miss Elisabeth Walton 1st 29.00 female 1 1
1 Allison, Miss Helen Loraine 1st 2.00 female 0 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.00 male 0 0
3 Allison, Mrs Hudson JC (Bessie Waldo Daniels) 1st 25.00 female 0 1
4 Allison, Master Hudson Trevor 1st 0.92 male 1 0

There are three important things to notice in this data frame.


First, in a data frame each row corresponds to one observation (e.g., a passenger) and each column
corresponds to one feature (gender, age, etc.). For example, by looking at the first observation we can
see that Miss Elisabeth Walton Allen stayed in first class, was 29 years old, was female, and survived
the disaster.
Second, each column contains a name (e.g., Name, PClass, Age) and each row contains an index number
(e.g., 0 for the lucky Miss Elisabeth Walton Allen). We will use these to select and manipulate
observations and features.
Third, two columns, Sex and SexCode, contain the same information in different formats. In Sex, a
woman is indicated by the string female, while in SexCode, a woman is indicated by using the integer
1. We will want all our features to be unique, and therefore we will need to remove one of these
columns.
In this chapter, we will cover a wide variety of techniques to manipulate data frames using the pandas
library with the goal of creating a clean, well-structured set of observations for further preprocessing.

3.1 Creating a Data Frame

Problem
You want to create a new data frame.

Solution
pandas has many methods of creating a new DataFrame object. One easy method is to instantiate a
DataFrame using a Python dictionary. In the dictionary, each key is a column name and the value is a
list - where each item corresponds to a row:

# Load library
import pandas as pd

# Create a dictionary
dictionary = {
"Name": ['Jacky Jackson', 'Steven Stevenson'],
"Age": [38, 25],
"Driver": [True, False]
}

# Create DataFrame
dataframe = pd.DataFrame(dictionary)

# Show DataFrame
dataframe

Name Age Driver


0 Jacky Jackson 38 True
1 Steven Stevenson 25 False

It’s easy to add new columns to any dataframe using a list of values:

# Add a column for eye color


dataframe["Eyes"] = ["Brown", "Blue"]

# Show DataFrame
dataframe

Name Age Driver Eyes


0 Jacky Jackson 38 True Brown
1 Steven Stevenson 25 False Blue

Discussion
pandas offers what can feel like an infinite number of ways to create a DataFrame. In the real world,
creating an empty DataFrame and then populating it will almost never happen. Instead, our DataFrames
will be created from real data we have loading from other sources (e.g., a CSV file or database).

3.2 Getting Information about the Data

Problem
You want to view some characteristics of a DataFrame.

Solution
One of the easiest things we can do after loading the data is view the first few rows using head:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'
# Load data
dataframe = pd.read_csv(url)

# Show two rows


dataframe.head(2)

Name PClass Age Sex Survived SexCode


0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

We can also take a look at the number of rows and columns:

# Show dimensions
dataframe.shape

(1313, 6)

We can get descriptive statistics for any numeric columns using describe:

# Show statistics
dataframe.describe()

Age Survived SexCode


count 756.000000 1313.000000 1313.000000
mean 30.397989 0.342727 0.351866
std 14.259049 0.474802 0.477734
min 0.170000 0.000000 0.000000
25% 21.000000 0.000000 0.000000
50% 28.000000 0.000000 0.000000
75% 39.000000 1.000000 1.000000
max 71.000000 1.000000 1.000000

Additionally, the `info` method can also show some helpful information:
----
# Show info
dataframe.info()
----

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1313 entries, 0 to 1312
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Name 1313 non-null object
1 PClass 1313 non-null object
2 Age 756 non-null float64
3 Sex 1313 non-null object
4 Survived 1313 non-null int64
5 SexCode 1313 non-null int64
dtypes: float64(1), int64(2), object(3)
memory usage: 61.7+ KB

Discussion
After we load some data, it is a good idea to understand how it is structured and what kind of
information it contains. Ideally, we would view the full data directly. But with most real-world cases,
the data could have thousands to hundreds of thousands to millions of rows and columns. Instead, we
have to rely on pulling samples to view small slices and calculating summary statistics of the data.
In our solution, we are using a toy dataset of the passengers of the Titanic on her last voyage. Using
head we can take a look at the first few rows (five by default) of the data. Alternatively, we can use
tail to view the last few rows. With shape we can see how many rows and columns our DataFrame
contains. And finally, with describe we can see some basic descriptive statistics for any numerical
column.
It is worth noting that summary statistics do not always tell the full story. For example, pandas treats the
columns Survived and SexCode as numeric columns because they contain 1s and 0s. However, in this
case the numerical values represent categories. For example, if Survived equals 1, it indicates that the
passenger survived the disaster. For this reason, some of the summary statistics provided don’t make
sense, such as the standard deviation of the SexCode column (an indicator of the passenger’s gender).

3.3 Slicing DataFrames

Problem
Another random document with
no related content on Scribd:
The Project Gutenberg eBook of Early autumn
This ebook is for the use of anyone anywhere in the United States
and most other parts of the world at no cost and with almost no
restrictions whatsoever. You may copy it, give it away or re-use it
under the terms of the Project Gutenberg License included with this
ebook or online at www.gutenberg.org. If you are not located in the
United States, you will have to check the laws of the country where
you are located before using this eBook.

Title: Early autumn

Author: Louis Bromfield

Release date: December 14, 2023 [eBook #72406]

Language: English

Original publication: NYC: Frederick A. Stokes Company, 1926

Credits: Chuck Greif and the Online Distributed Proofreading Team


at https://www.pgdp.net (This file was produced from
images generously made available by The Internet Archive)

*** START OF THE PROJECT GUTENBERG EBOOK EARLY


AUTUMN ***
Chapter I, Chapter II
Chapter III, Chapter IV, Chapter V, Chapter VI, Chapter
VII, Chapter VIII, Chapter IX, Chapter X.

Louis Bromfield

EARLY AUTUMN
Copyright, 1926, by
Frederick A. Stokes Company

All rights reserved

FOR

LAURA CANFIELD WOOD

EARLY AUTUMN
CHAPTER I

There was a ball in the old Pentland house because for the first time in
nearly forty years there was a young girl in the family to be introduced to
the polite world of Boston and to the elect who had been asked to come on
from New York and Philadelphia. So the old house was all bedizened with
lanterns and bunches of late spring flowers, and in the bare, white-painted,
dignified hallway a negro band, hidden discreetly by flowers, sat making
noisy, obscene music.
Sybil Pentland was eighteen and lately returned from School in Paris,
whither she had been sent against the advice of the conservative members
of her own family, which, it might have been said, included in its
connections most of Boston. Already her great-aunt, Mrs. Cassandra
Struthers, a formidable woman, had gone through the list of eligible young
men—the cousins and connections who were presentable and possessed of
fortunes worthy of consideration by a family so solidly rich as the
Pentlands. It was toward this end that the ball had been launched and the
whole countryside invited, young and old, spry and infirm, middle-aged and
dowdy—toward this end and with the idea of showing the world that the
family had lost none of its prestige for all the lack of young people in its
ranks. For this prestige had once been of national proportions, though now
it had shrunk until the Pentland name was little known outside New
England. Rather, it might have been said that the nation had run away from
New England and the Pentland family, leaving it stranded and almost
forgotten by the side of the path which marked an unruly, almost barbaric
progress away from all that the Pentland family and the old house
represented.
Sybil’s grandfather had seen to it that there was plenty of champagne;
and there were tables piled with salads and cold lobster and sandwiches and
hot chicken in chafing-dishes. It was as if a family whose whole history had
been marked by thrift and caution had suddenly cast to the winds all
semblance of restraint in a heroic gesture toward splendor.
But in some way, the gesture seemed to be failing. The negro music
sounded wild and spirited, but also indiscreet and out of place in a house so
old and solemn. A few men and one or two women known for their
fondness for drink consumed a great deal of champagne, but only dulness
came of it, dulness and a kind of dead despair. The rich, the splendorous,
the gorgeous, the barbaric, had no place in rooms where the kind Mr.
Longfellow and the immortal Messrs. Emerson and Lowell had once sat
and talked of life. In a hallway, beneath the gaze of a row of ancestors
remarkable for the grimness of their faces, the music appeared to lose its
quality of abandon; it did not belong in this genteel world. On the fringes of
the party there was some drunkenness among the undergraduates imported
from Cambridge, but there was very little gaiety. The champagne fell upon
barren ground. The party drooped.
Though the affair was given primarily to place Sybil Pentland upon the
matrimonial market of this compact world, it served, too, as an introduction
for Thérèse Callendar, who had come to spend the summer at Brook
Cottage across the stony meadows on the other side of the river from
Pentlands; and as a reintroduction of her mother, a far more vivid and
remarkable person. Durham and the countryside thereabouts was familiar
enough to her, for she had been born there and passed her childhood within
sight of the spire of the Durham town meeting-house. And now, after an
absence of twenty years, she had come back out of a world which her own
people—the people of her childhood—considered strange and ungenteel.
Her world was one filled with queer people, a world remote from the quiet
old house at Pentlands and the great brownstone houses of Commonwealth
Avenue and Beacon Street. Indeed, it was this woman, Sabine Callendar,
who seemed to have stolen all the thunder at the ball; beside her, neither of
the young girls, her own daughter nor Sybil Pentland, appeared to attract
any great interest. It was Sabine whom every one noticed, acquaintances of
her childhood because they were devoured by curiosity concerning those
missing twenty years, and strangers because she was the most picturesque
and arresting figure at the ball.
It was not that she surrounded herself by adoring young men eager to
dance with her. She was, after all, a woman of forty-six, and she had no
tolerance for mooning boys whose conversation was limited to bootlegging
and college clubs. It was a success of a singular sort, a triumph of
indifference.
People like Aunt Cassie Struthers remembered her as a shy and awkward
young girl with a plain face, a good figure and brick-red hair which twenty
years ago had been spoken of as “Poor Sabine’s ugly red hair.” She was a
girl in those days who suffered miserably at balls and dinners, who shrank
from all social life and preferred solitude. And now, here she was—returned
—a tall woman of forty-six, with the same splendid figure, the same long
nose and green eyes set a trifle too near each other, but a woman so striking
in appearance and the confidence of her bearing that she managed somehow
to dim the success even of younger, prettier women and virtually to
extinguish the embryonic young things in pink-and-white tulle. Moving
about indolently from room to room, greeting the people who had known
her as a girl, addressing here and there an acquaintance which she had made
in the course of the queer, independent, nomadic life she had led since
divorcing her husband, there was an arrogance in her very walk that
frightened the young and produced in the older members of Durham
community (all the cousins and connections and indefinable relatives), a
sense of profound irritation. Once she had been one of them, and now she
seemed completely independent of them all, a traitress who had flung to the
winds all the little rules of life drilled into her by Aunt Cassie and other
aunts and cousins in the days when she had been an awkward, homely little
girl with shocking red hair. Once she had belonged to this tight little world,
and now she had returned—a woman who should have been defeated and a
little declassée and somehow, irritatingly, was not. Instead, she was a
“figure” much sought after in the world, enveloped by the mysterious cloud
of esteem which surrounds such persons—a woman, in short, who was able
to pick her friends from the ranks of distinguished and even celebrated
people. It was not only because this was true, but because people like Aunt
Cassie knew it was true, that she aroused interest and even indignation. She
had turned her back upon them all and no awful fate had overtaken her;
instead, she had taken a firm hold upon life and made of it a fine, even a
glittering, success; and this is a thing which is not easily forgiven.
As she moved through the big rooms—complete and perfect from her
superbly done, burnished red hair to the tips of her silver slippers—there
was about her an assurance and an air of confidence in her own perfection
that bordered upon insolence. There was a hard radiance and beauty in the
brilliant green dress and the thin chain of diamonds that dimmed all of the
others, that made most of the women seem dowdy and put together with
pins. Undoubtedly her presence also served to dampen the gaiety. One knew
from the look in the disdainful green eyes and the faint mocking smile on
the frankly painted red mouth that she was aware of the effect she made and
was delighted with her triumph. Wherever she went, always escorted by
some man she had chosen with the air of conferring a favor, a little stir
preceded her. She was indeed very disagreeable....
If she had a rival in all the crowd that filled the echoing old house, it was
Olivia Pentland—Sybil’s mother—who moved about, alone most of the
time, watching her guests, acutely conscious that the ball was not all it
should have been. There was about her nothing flamboyant and arresting,
nothing which glittered with the worldly hardness of the green dress and the
diamonds and burnished red hair of Sabine Callendar; she was, rather, a soft
woman, of gentleness and poise, whose dark beauty conquered in a slower,
more subtle fashion. You did not notice her at once among all the guests;
you became aware of her slowly, as if her presence had the effect of stealing
over you with the vagueness of a perfume. Suddenly you marked her from
among all the others ... with a sense of faint excitement ... a pale white face,
framed by smooth black hair drawn back low over the brows in a small knot
at the back of her head. You noticed the clear, frank blue eyes, that in some
lights seemed almost black, and most of all you noticed when she spoke
that her voice was low, warm, and in a way irresistible, a voice with a
hundred shades of color. She had a way, too, of laughing, when she was
struck by the absurdity of something, that was like a child. One knew her at
once for a great lady. It was impossible to believe that she was nearly forty
and the mother of Sybil and a boy of fifteen.
Circumstance and a wisdom of her own had made of her a woman who
seemed inactive and self-effacing. She had a manner of doing things
effortlessly, with a great quietness, and yet, after one came to know her, one
felt that she missed little which took place within sight or hearing—not only
the obvious things which any stupid person might have noticed, but the
subtle, indefinite currents which passed from one person to another. She
possessed, it seemed, a marvelous gift for smoothing out troubles. A
security, of the sort which often marks those who suffer from a too great
awareness, enveloped and preceded her, turning to calm all the troubled
world about her. Yet she was disturbing, too, in an odd, indefinable way.
There was always a remoteness and a mystery, a sense almost of the fey. It
was only after one had known her for a long time, enveloped in the
quietness of her pleasant presence, that a faint sense of uneasiness was
born. It would occur to you, with the surprise almost of a shock, that the
woman you saw before you, the woman who was so gentle and serene, was
not Olivia Pentland at all, but a kind of clay figure which concealed, far
beneath the veneer of charm, a woman you did not know at all, who was
remote and sad and perhaps lonely. In the end, she disturbed the person of
discernment far more profoundly than the glittering, disagreeable Sabine
Callendar.
In the midst of the noise and confusion of the ball, she had been moving
about, now in this big room, now in that one, talking quietly to her guests,
watching them, seeing that all went well; and, like all the others, she was
fascinated at the spectacle of Sabine’s rebellion and triumph, perhaps even a
little amused at the childishness of such defiance in a woman of forty-six
who was clever, independent and even distinguished, who need not have
troubled to flaunt her success.
Watching Sabine, whom she knew intimately enough, she had guessed
that underneath the shell made so superbly by hairdresser, couturier and
jeweler there lay hidden an awkward, red-haired little girl who was having
her revenge now, walking roughshod over all the prejudices and traditions
of such people as Aunt Cassie and John Pentland and Cousin Struthers
Smallwood, D.D., whom Sabine always called “the Apostle to the Genteel.”
It was almost, thought Olivia, as if Sabine, even after an exile of twenty
years, was still afraid of them and that curious, undefeatable power which
they represented.
But Sabine, she knew, was observing the party at the same time. She had
watched her all the evening in the act of “absorbing” it; she knew that when
Sabine walked across from Brook Cottage the next day, she would know
everything that had happened at the ball, for she had a passion for
inspecting life. Beneath the stony mask of indifference there boiled a
perpetual and passionate interest in the intricacies of human affairs. Sabine
herself had once described it as “the curse of analysis which took all the
zest out of life.”
She was fond of Sabine as a creature unique in the realm of her
experience, one who was amusing and actually made fetishes of truth and
reality. She had a way of turning her intellect (for it was really a great
intellect) upon some tangled, hopeless situation to dissolve it somehow into
its proper elements and make it appear suddenly clear, uncomplicated and,
more often than not, unpleasant; because the truth was not always a sweet
and pleasant thing.
2

No one suffered more keenly from Sabine’s triumphant return than the
invincible Aunt Cassie. In a way, she had always looked upon Sabine, even
in the long years of her voluntary exile from the delights of Durham, as her
own property, much as she might have looked upon a dog, if, indeed, the
old lady had been able to bear the society of anything so untidy as a dog.
Childless herself, she had exercised all her theories of upbringing upon the
unfortunate orphaned little daughter of her husband’s brother.
At the moment, the old lady sat half-way down the white stairs, her
sharp, black eyes surveying the ball with a faint air of disapproval. The
noisy music made her nervous and uneasy, and the way young girls had of
using paint and powder seemed to her cheap and common. “One might as
well brush one’s teeth at the dinner-table.” Secretly, she kept comparing
everything with the ball given for herself forty years earlier, an event which
had resulted at length in the capture of Mr. Struthers. Dressed economically
(for she made it a point of honor to live on the income of her income), and
in mourning for a husband dead eight years earlier, she resembled a
dignified but slightly uneasy crow perched on a fence.
It was Sabine who observed that Aunt Cassie and her “lady companion,”
Miss Peavey, sitting on the steps together, resembled a crow and a pouter
pigeon. Miss Peavey was not only fat, she was actually bulbous—one of
those women inclined by nature toward “flesh,” who would have been fat
on a diet of sawdust and distilled water; and she had come into the family
life nearly thirty years earlier as a companion, a kind of slave, to divert
Aunt Cassie during the long period of her invalidism. She had remained
there ever since, taking the place of a husband who was dead and children
who had never been born.
There was something childlike about Miss Peavey—some people said
that she was not quite bright—but she suited Aunt Cassie to a T, for she was
as submissive as a child and wholly dependent in a financial sense. Aunt
Cassie even gave her enough to make up for the losses she incurred by
keeping a small shop in Boston devoted to the sale of “artistic” pottery.
Miss Peavey was a lady, and though penniless, was “well connected” in
Boston. At sixty she had grown too heavy for her birdlike little feet and so
took very little exercise. To-night she was dressed in a very fancy gown
covered with lace and sequins and passementerie, rather in the mode which
some one had told her was her style in the far-off days of her girlhood. Her
hair was streaked with gray and cut short in a shaggy, uneven fashion; not,
however, because short hair was chic, but because she had cut it ten years
before short hair had been heard of, in a sudden futile gesture of freedom at
the terrible moment she made her one feeble attempt to escape Aunt Cassie
and lead her own life. She had come back in the end, when her poor savings
gave out and bankruptcy faced her, to be received by Aunt Cassie with
dignified sighs and flutters as a returned and repentant prodigal. In this rôle
she had lived ever since in a state of complete subjection. She was Aunt
Cassie’s creature now, to go where Aunt Cassie ordered, to do as she was
bid, to be an ear-piece when there was at hand no one more worthy of
address.
At the sight of Sabine’s green dress and red hair moving through the big
hall below them, Aunt Cassie said, with a gleam in her eye: “Sabine seems
to be worried about her daughter. The poor child doesn’t seem to be having
a success, but I suppose it’s no wonder. The poor thing is very plain. I
suppose she got the sallow skin from her father. He was part Greek and
French.... Sabine was never popular as a young girl herself.”
And she fell to speculating for the hundredth time on the little-known
circumstances of Sabine’s unhappy marriage and divorce, turning the
morsels over and over again with a variety of speculation and the
interjection of much pious phraseology; for in Aunt Cassie’s speech God
seemed to have a hand in everything. He had a way of delivering trials and
blessings indiscriminately, and so in the end became responsible for
everything.
Indeed, she grew a bit spiteful about Sabine, for there was in the back of
her mind the memory of an encounter, a day or two earlier, when she had
been put completely to rout. It was seldom that Aunt Cassie met any one
who was a match for her, and when such an encounter took place the
memory of it rankled until she found some means of subduing the offender.
With Miss Peavey she was completely frank, for through long service this
plump, elderly virgin had come to be a sort of confessor in whose presence
Aunt Cassie wore no mask. She was always saying, “Don’t mind Miss
Peavey. She doesn’t matter.”
“I find Sabine extremely hard and worldly,” she was saying. “I would
never know her for the same modest young girl she was on leaving me.”
She sighed abysmally and continued, “But, then, we mustn’t judge. I
suppose the poor girl has had a great deal of misery. I pity her to the depths
of my heart!”
In Aunt Cassie’s speeches, in every phrase, there was always a certain
mild theatrical overtone as if she sought constantly to cast a sort of
melodramatic haze over all she said. Nothing was ever stated simply.
Everything from the sight of a pot of sour cream to the death of her husband
affected her extravagantly, to the depths of her soul.
But this brought no response from Miss Peavey, who seemed lost in the
excitement of watching the young people, her round candid eyes shining
through her pince-nez with the eagerness of one who has spent her whole
life as a “lady companion.” At moments like this, Aunt Cassie felt that Miss
Peavey was not quite bright, and sometimes said so.
Undiscouraged, she went on. “Olivia looks bad, too, to-night ... very
tired and worn. I don’t like those circles under her eyes.... I’ve thought for a
long time that she was unhappy about something.”
But Miss Peavey’s volatile nature continued to lose itself completely in
the spectacle of young girls who were so different from the girls of her day;
and in the fascinating sight of Mr. Hoskins, a fat, sentimental, middle-aged
neighbor who had taken a glass too much champagne and was talking
archly to the patient Olivia. Miss Peavey had quite forgotten herself in the
midst of so much gaiety. She did not even see the glances of Aunt Cassie in
her direction—glances which plainly said, “Wait until I get you alone!”
For a long time Aunt Cassie had been brooding over what she called
“Olivia’s strange behavior.” It was a thing which she had noticed for the
first time a month or two earlier when Olivia, in the midst of one of Aunt
Cassie’s morning calls, had begun suddenly, quietly, to weep and had left
the room without a word of explanation. It had gone from bad to worse
lately; she felt Olivia slipping away from all control directly in opposition
to her own benevolent advice. There was the matter of this very ball. Olivia
had ignored her counsels of economy and thrift, and now Aunt Cassie was
suffering, as if the champagne which flowed so freely were blood drawn
from her own veins. Not for a century, since Savina Pentland purchased a
parure of pearls and emeralds, had so much Pentland money been expended
at one time on mere pleasure.
She disapproved, too, of the youthfulness of Olivia and of Sabine.
Women of their ages ought not to look so fresh and young. There was
something vulgar, even a little improper, in a woman like Sabine who at
forty-six looked thirty-five. At thirty, Aunt Cassie herself had settled down
as a middle-aged woman, and since then she had not changed greatly. At
sixty-five, “childless and alone in the world” (save, of course, for Miss
Peavey), she was much the same as she had been at thirty in the rôle of wife
to the “trying Mr. Struthers.” The only change had been her recovery from a
state of semi-invalidism, a miracle occurring simultaneously with the
passing of Mr. Struthers.
She had never quite forgiven Olivia for being an outsider who had come
into the intricate web of life at Pentlands out of (of all places) Chicago.
Wisps of mystery and a faint sense of the alien had clung to her ever since.
Of course, it wasn’t to be expected that Olivia could understand entirely
what it meant to marry into a family whose history was so closely woven
into that of the Massachusetts Bay Colony and the life of Boston. What
could it mean to Olivia that Mr. Longfellow and Mr. Lowell and Dr.
Holmes had often spent weeks at Pentlands? That Mr. Emerson himself had
come there for week-ends? Still (Aunt Cassie admitted to herself), Olivia
had done remarkably well. She had been wise enough to watch and wait
and not go ahead strewing her path with blunders.
Into the midst of these thoughts the figure of Olivia herself appeared,
moving toward the stairway, walking beside Sabine. They were laughing
over something, Sabine in the sly, mocking way she had, and Olivia
mischievously, with a suspicious twinkle in her eyes. Aunt Cassie was filled
with an awful feeling that they were sharing some joke about the people at
the ball, perhaps even about herself and Miss Peavey. Since Sabine had
returned, she felt that Olivia had grown even more strange and rebellious;
nevertheless, she admitted to herself that there was a distinction about them
both. She preferred the quiet distinction of Olivia to the violence of the
impression made by the glittering Sabine. The old lady sensed the
distinction, but, belonging to a generation which lived upon emotion rather
than analysis, she did not get to the root of it. She did not see that one felt at
once on seeing Olivia, “Here is a lady!”—perhaps, in the true sense of the
word, the only lady in the room. There was a gentleness about her and a
softness and a proud sort of poise—all qualities of which Aunt Cassie
approved; it was the air of mystery which upset the old lady. One never
knew quite what Olivia was thinking. She was so gentle and soft-spoken.
Sometimes of late, when pressing Olivia too hotly, Aunt Cassie, aware of
rousing something indefinably perilous in the nature of the younger woman,
drew back in alarm.
Rising stiffly, the old lady groaned a little and, moving down the stairs,
said, “I must go, Olivia dear,” and, turning, “Miss Peavey will go with me.”
Miss Peavey would have stayed, because she was enjoying herself,
looking down on all those young people, but she had obeyed the commands
of Aunt Cassie for too long, and now she rose, complaining faintly, and
made ready to leave.
Olivia urged them to stay, and Sabine, looking at the old lady out of
green eyes that held a faint glitter of hatred, said abruptly: “I always
thought you stayed until the bitter end, Aunt Cassie.”
A sigh answered her ... a sigh filled with implications regarding Aunt
Cassie’s position as a lonely, ill, bereft, widowed creature for whom life
was finished long ago. “I am not young any longer, Sabine,” she said. “And
I feel that the old ought to give way to the young. There comes a time....”
Sabine gave an unearthly chuckle. “Ah,” she said, in her hard voice, “I
haven’t begun to give up yet. I am still good for years.”
“You’re not a child any more, Sabine,” the old lady said sharply.
“No, certainly I’m not a child any more.” And the remark silenced Aunt
Cassie, for it struck home at the memory of that wretched scene in which
she had been put to rout so skilfully.
There was a great bustle about getting the two old ladies under way, a
great search for cloaks and scarfs and impedimenta; but at last they went
off, Aunt Cassie saying over her thin, high shoulder, “Will you say good-by
to your dear father-in-law, Olivia? I suppose he’s playing bridge with Mrs.
Soames.”
“Yes,” replied Olivia from the terrace, “he’s playing bridge with Mrs.
Soames.”
Aunt Cassie merely cleared her throat, forcibly, and with a deep
significance. In her look, as in the sound of her voice, she managed to
launch a flood of disapproval upon the behavior of old John Pentland and
old Mrs. Soames.
Bidding the driver to go very slowly, she climbed into her shabby,
antiquated motor, followed respectfully by Miss Peavey, and drove off
down the long elm-bordered drive between the lines of waiting motors.
Olivia’s “dear father-in-law” was Aunt Cassie’s own brother, but she
chose always to relate him to Olivia, as if in some way it bound Olivia more
closely, more hopelessly, into the fabric of the family.

As the two younger women reentered the house, Olivia asked, “Where’s
Thérèse? I haven’t seen her for more than an hour.”
“She’s gone home.”
“Thérèse ... gone home ... from a ball given for her!”
Olivia halted in astonishment and stood leaning against the wall, looking
so charming and lovely that Sabine thought, “It’s a sin for a woman so
beautiful to have such a life.”
Aloud Sabine said, “I caught her stealing away. She walked across to the
cottage. She said she hated it and was miserable and bored and would rather
be in bed.” Sabine shrugged her handsome shoulders and added, “So I let
her go. What difference does it make?”
“None, I suppose.”
“I never force her to do things of this sort. I had too much forcing when I
was young; Thérèse is to do exactly as she likes and be independent. The
trouble is, she’s been spoilt by knowing older men and men who talk
intelligently.” She laughed and added, “I was wrong about coming back
here. I’ll never marry her off in this part of the world. The men are all afraid
of her.”
Olivia kept seeing the absurd figure of Sabine’s daughter, small and
dark, with large burning eyes and an air of sulky independence, striding off
on foot through the dust of the lane that led back to Brook Cottage. She was
so different from her own daughter, the quiet, well-mannered Sybil.
“I don’t think she’s properly impressed by Durham,” said Olivia, with a
sudden mischievous smile.
“No ... she’s bored by it.”
Olivia paused to say good-night to a little procession of guests ... the
Pingree girls dressed alike in pink tulle; the plump Miss Perkins, who had
the finest collection of samplers in New England; Rodney Phillips, whose
life was devoted to breeding springers and behaving like a perfect English
gentleman; old Mr. Tilney, whose fortune rested on the mills of Durham and
Lynn and Salem; and Bishop Smallwood, a cousin of the Pentlands and
Sabine (whom Sabine called the Apostle of the Genteel). The Bishop
complimented Olivia on the beauty of her daughter and coquetted heavily
with Sabine. Motors rushed out from among the lilacs and syringas and
bore them away one by one.
When they had gone Sabine said abruptly, “What sort of man is this
Higgins ... I mean your head stableman?”
“A good sort,” replied Olivia. “The children are very fond of him.
Why?”
“Oh ... no reason at all. I happened to think of him to-night because I
noticed him standing on the terrace just now looking in at the ball.”
“He was a jockey once ... a good one, I believe, until he got too heavy.
He’s been with us ten years. He’s good and reliable and sometimes very
funny. Old Mr. Pentland depends on him for everything.... Only he has a
way of getting into scrapes with the girls from the village. He seems
irresistible to them ... and he’s an immoral scamp.”
Sabine’s face lighted up suddenly, as if she had made a great discovery.
“I thought so,” she observed, and wandered away abruptly to continue the
business of “absorbing” the ball.
She had asked about Higgins because the man was stuck there in her
brain, set in the midst of a strange, confused impression that disturbed a
mind usually marked by precision and clarity. She did not understand why it
was that he remained the most vivid of all the kaleidoscopic procession of
the ball. He had been an outsider, a servant, looking in upon it, and yet there
he was—a man whom she had never noticed before—vivid and clear-cut,
dominating the whole evening.
It had happened a little earlier when, standing in the windowed alcove of
the old red-paneled writing-room, she had turned her back for a moment on
the ball, to look out upon the distant marshes and the sea, across meadows
where every stone and tree and hedge was thrown into a brilliant relief by
the clarity of the moonlight and the thin New England air. And trapped
suddenly by the still and breathless beauty of the meadows and marshes and
distant white dunes, lost in memories more than twenty years old, she had
found herself thinking: “It was always like this ... rather beautiful and hard
and cold and a little barren, only I never saw it before. It’s only now, when
I’ve come back after twenty years, that I see my own country exactly as it
is.”
And then, standing there quite alone, she had become aware slowly that
she was being watched by some one. There was a sudden movement among
the lilacs that stood a little way off wrapped in thick black shadows ... the
faintest stirring of the leaves that drew her sharply back to a consciousness
of where she was and why she was there; and, focusing all her attention, she
was able to make out presently a short, stocky little figure, and a white face
peering out from among the branches, watching the dancers who moved
about inside the house. The sight produced in her suddenly a sensation of
uneasiness and a faint prickling of the skin, which slipped away presently
when she recognized the odd, prematurely wrinkled face of Higgins, the
Pentland groom. She must have seen him a dozen times before, barely
noticing him, but now she saw him with a kind of illuminating clarity, in a
way which made his face and figure unforgettable.
He was clad in the eternal riding-breeches and a sleeveless cotton shirt
that exposed the short, hairy, muscular arms. Standing there he seemed,
with his arched, firmly planted legs, like some creature rooted into the soil
... like the old apple-tree which stood in the moonlight showering the last of
its white petals on the black lawn. There was something unpleasant in the
sight, as if (she thought afterwards) she had been watched without knowing
it by some animal of an uncanny intelligence.
And then abruptly he had slipped away again, shyly, among the branches
of the lilacs ... like a faun.

Olivia, looking after Sabine as she walked away, smiled at the


knowledge of where she was bound. Sabine would go into the old writing-
room and there, sitting in a corner, would pretend that she was interested in
the latest number of the Mercure de France or some fashion paper, and all
the time she would be watching, listening, while old John Pentland and
poor battered old Mrs. Soames sat playing bridge with a pair of
contemporaries. Sabine, she knew, wanted to probe the lives of the two old
people. She wasn’t content like the others at Pentlands to go on pretending
that there had never been anything between them. She wanted to get to the
root of the story, to know the truth. It was the truth, always the truth, which
fascinated Sabine.
And Olivia felt a sudden, swift, almost poignant wave of affection for
the abrupt, grim woman, an affection which it was impossible to express
because Sabine was too scornful of all sentiment and too shut in ever to
receive gracefully a demonstration; yet she fancied that Sabine knew she
was fond of her, in the same shy, silent way that old John Pentland knew
she was fond of him. It was impossible for either of them ever to speak of
such simple things as affection.
Since Sabine had come to Durham, it seemed to Olivia that life was a
little less barren and not quite so hopeless. There was in Sabine a curious
hard, solid strength which the others, save only the old man, lacked
completely. Sabine had made some discovery in life that had set her free ...
of everything but that terrible barrier of false coldness.
In the midst of these thoughts came another procession of retreating
guests, and the sadness, slipping away from Olivia’s face, gave way to a
perfect, artificial sort of gaiety. She smiled, she murmured, “Good-night,
must you go,” and, “Good-night, I’m so glad that you liked the ball.” She
was arch with silly old men and kind to the shy young ones and repeated the
same phrases over and over again monotonously. People went away saying,
“What a charming woman Olivia Pentland is!”
Yet immediately afterward she did not remember who had passed by her.
One by one the guests departed, and presently the black musicians
packed up their instruments and went away, and at last Sybil appeared, shy
and dark, looking a little pale and tired in her clinging gown of pale green.
At sight of her daughter a little thrill of pride ran through Olivia. She was
the loveliest of all the girls at the ball, not the most flamboyant, but the
gentlest and really the most beautiful. She possessed the same slow beauty
of her mother, which enveloped one in a kind of mist that lingered long
after she herself had gone away. She was neither loud and mannish and
vulgar like the “horsey” women nor common like the girls who used too
much paint and tried to behave like women of the world. There was already
about her the timelessness that envelops a lady no matter the generation in
which she appears; there was a mystery, a sophistication and knowledge of
life which put to rout all the cheap flashiness of the others. And yet,
somehow, that same cool, shy poise and beauty frightened people. Boys
who were used to calling young girls “Good old So-and-so” found
themselves helpless before the dignity of a young girl who looked in her
green gown a little like a cool wood-nymph. It troubled Olivia profoundly,
not for herself, but because she wanted the girl to be happy—more than
that, to know the depths of happiness which she herself had sensed but
never found. It was in a way as if she saw herself again in Sybil, as if
looking back now from the pinnacle of her own experience she could guide
this younger self, standing on the brink of life, along paths less barren than
those trod by her own feet. It was so necessary that Sybil should fall in love
with a man who would make her happy. With most girls it would make little
difference one way or another, so long as they had money; if they were
unhappy or bored they would divorce their husbands and try again because
that was the rule in their world. But with Sybil, marriage would be either an
immense, incalculable happiness or a profound and hopeless tragedy.
She thought suddenly of what Sabine had said of Thérèse a little while
before. “I was wrong about coming back here. I’ll never marry her off in
this part of the world.”
It was true somehow of Sybil. The girl, in some mysterious fashion,
knew what it was she wanted; and this was not a life which was safe and
assured, running smoothly in a rigid groove fixed by tradition and
circumstance. It was not marriage with a man who was like all the other
men in his world. It went deeper than all that. She wanted somehow to get
far down beneath the surface of that life all about her, deep down where
there was a savor to all she did. It was a hunger which Olivia understood
well enough.
The girl approached her mother and, slipping her arm about her waist,
stood there, looking for all the world like Olivia’s sister.
“Have you enjoyed it?” asked Olivia.
“Yes.... It’s been fun.”
Olivia smiled. “But not too much?”
“No, not too much.” Sybil laughed abruptly, as if some humorous
memory had suddenly come to life.
“Thérèse ran away,” said her mother.
“I know ... she told me she was going to.”

You might also like