GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||
GeezerVison is showing ucmerced.github.io — Programming in Python
Fundamentals (Week 1)OrientationWhat programming language should I use?
Python is pretty good at lots of things
Literate programming and notebooks
Jupyter commandsHow to start Jupyter Lab
Navigation
Writing code
Variables and AssignmentUse variables to store valuesVariables are names for values. Rules for naming things
Use
|
| Shape of data selection | Pandas return type |
|---|---|
| 2D | DataFrame |
| 1D | Series |
| 0D | single value |
Use method chaining
print(subset.max().max())
.filter() always returns the same type as the original item, whereas .loc and .iloc might return a data frame or a series.
italy = data.filter(items=["Italy"], axis="index")
print(italy)
print(type(italy))
.filter() is a general-purpose, flexible method
help(data.filter)
data.filter(like="200", axis="columns")
data.filter(like="200", axis="columns").filter(items=["Italy"], axis="index")
Show which data frame elements match a criterion.
# Which GDPs are greater than 10,000?
subset > 10000
Use the criterion match to filter the data frame’s contents. This uses index notation:
fs = subset[subset > 10000]
print(fs)
subset > 10000 returns a data frame of True/False valuessubset[subset > 10000] filters its contents based on that True/False data frameNaN.(Optional) Use .where() method to find elements that match the criterion:
fs = subset.where(subset > 10000)
print(fs)
# GDP for all countries greater than the median
subset[subset > subset.median()]
# OR: subset.where(subset > subset.median())
# The .rank() method turns numerical scores into ranks
subset.rank()
# GDP ranking for all countries greater than the median
subset[subset > subset.median()].rank()
# OR: subset.where(subset > subset.median()).rank()
Examples include min, max, mean, std, etc.
Missing values ignored by default
print("Column means")
print(fs.mean())
print("Row means")
print(fs.mean(axis=1))
Force inclusions with the skipna argument
print("Column means")
print(fs.mean(skipna=False))
print("Row means")
print(fs.mean(axis=1, skipna=False))
Show which items are missing. “NA” includes NaN and None. It doesn’t include empty strings or numpy.inf.
# Show which items are NA
fs.isna()
Count missing values
# Missing by row
print(fs.isna().sum())
# Missing by column
print(fs.isna().sum(axis=1))
# Aggregate sum
fs.isna().sum().sum()
Are any values missing?
fs.isna().any(axis=None)
(Optional) Are all of the values missing?
fs.isna().all(axis=None)
Replace with a fixed value
fs_fixed = fs.fillna(99)
print(fs_fixed)
Replace values that don’t meet a criterion with an alternate value
subset_fixed = subset.where(subset > 10000, 99)
print(subset_fixed)
(Optional) Impute missing values. Read the docs, this may or may not be sufficient for your needs.
fs_imputed = fs.interpolate()
Drop all rows with missing values
fs_drop = fs.dropna()
A DataFrame is a dictionary of Series columns. With this in mind, experiment with the following code and try to explain what each line is doing. What operation is it performing, and what is being returned?
Feel free to use print(), help(), type(), etc as you investigate.
fs["1962"]
fs["1962"].notna()
fs[fs["1962"].notna()]
# Calculate z scores for all elements
z = (data - data.mean())/data.std()
# Get the mean z score for each country (i.e. across all columns)
mean_z = z.mean(axis=1)
# Group countries into "wealthy" (z > 0) and "not wealthy" (z <= 0)
z_bool = mean_z > 0
print(mean_z)
print(z_bool)
Data frames are dictionaries of Series:
data["mean_z"] = mean_z
data["wealthy"] = z_bool
data.sort_values(by="mean_z")
# Get descriptive statistics for the group
data.groupby("wealthy").mean()
data.groupby("wealthy").describe()
Capture the results of your filter in a new file, rather than overwriting your original data.
# Save to a new CSV, preserving your original data
data.to_csv('gapminder_gdp_europe_normed.csv')
# If you don't want to preserve row names:
#data.to_csv('gapminder_gdp_europe_normed.csv', index=False)
surveys = pd.read_csv('data/surveys.csv', index_col="record_id")
print(surveys.shape)
df1 = surveys.head(10)
df2 = surveys.tail(10)
df3 = pd.concat([df1, df2])
print(df3.shape)
Import species data
species = pd.read_csv('data/species.csv', index_col="species_id")
print(species.shape)
Join tables on common column. The “left” join is a strategy for augmenting the first table (surveys) with information from the second table (species).
df_join = surveys.merge(species, on="species_id", how="left")
print(df_join.head())
print(df_join.shape)
The resulting table loses its index because surveys.record_id is not being used in the join. To keep record_id as the index for the final table, we need to retain it as an explicit column.
# Don't set record_id as index during initial import
surveys = pd.read_csv('data/surveys.csv')
df_join = surveys.merge(species, on="species_id", how="left").set_index("record_id")
df_join.index
Get the subset of species that match a criterion, and join on that subset. The “inner” join only includes rows where both tables match on the key column; it’s a strategy for filtering the first table by the second table.
# Get the taxa column, masking the rows based on which values match "Bird"
birds = species[species["taxa"] == "Bird"]
df_birds = surveys.join(birds, on="species_id").set_index("record_id")
print(df_birds.head())
print(df_birds.shape)
cf. https://pandas.pydata.org/docs/user_guide/text.html
Import tabular data that contains strings
species = pd.read_csv('data/species.csv', index_col='species_id')
# You can explicitly set all of the columns to type string
# species = pd.read_csv('data/species.csv', index_col='species_id', dtype='string')
# ...or specify the type of individual columns
# species = pd.read_csv('data/species.csv', index_col='species_id',
# dtype = {"genus": "string",
# "species": "string",
# "taxa": "string"})
print(species.head())
print(species.info())
print(species.describe())
A Pandas Series has string methods that operate on the entire Series at once
# Two ways of getting an individual column
print(type(species.genus))
print(type(species["genus"]))
# Inspect the available string methods
print(dir(species["genus"].str))
Use string methods for filtering
# Which species are in the taxa "Bird"?
print(species["taxa"].str.startswith("Bird"))
# Filter the dataset to only look at Birds
print(species[species["taxa"].str.startswith("Bird")])
Use string methods to transform and combine data
binomial_name = species["genus"].str.cat(species["species"].str.title(), " ")
species["binomial"] = binomial_name
print(species.head())
A row is a view onto the nth item of each of the column Series. Appending rows is a performance bottleneck because it requires a separate append operation for each Series. You should concatenate data frames instead.
Create a single row as a data frame and concatenate it.
row = pd.DataFrame({"1962": 5000, "1967": 5000, "1972": 5000}, index=["Latveria"])
pd.concat([subset, row])
If you have individual rows as Series, pd.concat() will produce a data frame.
# Get each row as a Series
italy = data.loc["Italy", :]
poland = data.loc["Poland", :]
# Omitting axis argument (or axis=0) concatenates the 2 series end-to-end
# axis=1 creates a 2D data frame
# Transpose recovers original orientation
# Column labels come from Series index
# Row labels come from Series name
pd.concat([italy, poland], axis=1).T
Scikit-Learn documentation: https://scikit-learn.org/stable/
Motivating example: Ordinary least squares regression
from sklearn import linear_model
# Create some random data
x_train = np.random.rand(20)
y = np.random.rand(20)
# Fit a linear model
reg = linear_model.LinearRegression()
reg.fit(x_train.reshape(-1,1), y)
print("Regression slope:", reg.coef_)
Estimate model fit
from sklearn.metrics import r2_score
# Test model fit with new data
x_test = np.random.rand(20)
y_prediction = reg.predict(x_test.reshape(-1,1))
# Get model stats
mse = mean_squared_error(y, y_prediction)
r2 = r2_score(y, y_prediction)
print("R squared:", "{:.3f}".format(r2))
(Optional) Inspect our prediction
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter(x_train, y, color="black")
ax.plot(x_test, y_prediction, color="blue")
# `fig` in Jupyter Lab
fig.show()
(Optional) Compare with Statsmodels
# Load modules and data
import statsmodels.api as sm
# Fit and summarize OLS model (center data to get accurate model fit
mod = sm.OLS(y - y.mean(), x_train - x_train.mean())
res = mod.fit()
print(res.summary())
Import data
data = pd.read_csv('surveys.csv')
# Check for NaN
print("Valid weights:", data['weight'].count())
print("NaN weights:", data['weight'].isna().sum())
print("Valid lengths:", data['hindfoot_length'].count())
print("NaN lengths:", data['hindfoot_length'].isna().sum())
Fit OLS regression model
from statsmodels.formula.api import ols
model = ols("weight ~ hindfoot_length", data, missing='drop').fit()
print(model.summary())
Generic parameters for all models
import statsmodels
help(statsmodels.base.model.Model)
wine.rename(columns={"color_intensity": "ci"})
.assign(color_filter=lambda x: np.where((x.hue > 1) & (x.ci > 7), 1, 0))
.query("alcohol > 14 and color_filter == 1")
.sort_values("alcohol", ascending=False)
.reset_index(drop=True)
.loc[:, ["alcohol", "ci", "hue"]]
DataFrames have a huge number of fields and methods, so dir() is not very useful
print(dir(data))
Create a new list that filters out internal attributes
df_joinpublic = [item for item in dir(data) if not item.startswith('_')]
print(df_public)
(Optional) Pretty-print the new list
importort pprint
pp = pprint.PrettyPrinter(width=100, compact=True, indent=2)
pp.pprint(df_public)
Objects have fields (i.e. data/variables) and methods (i.e. functions/procedures). The difference between a method and a function is that methods are attached to objects, whereas functions are free-floating (“first-class citizens”). Methods and functions are “callable”:
# GeneratorExitenerate a list of public methods and a list of public fields. We do this
# by testing each attribute to determine whether it is "callable".
# NB: Because Python allows you to override any attribute at runtime,
# testing with `callable` is not always reliable.
# List of methods (callable attributes)
df_methods = [item for item in dir(data) if not item.startswith('_')
and callable(getattr(data, item))]
# List of fields (non-callable attributes)
df_attr = [item for item in dir(data) if not item.startswith('_')
and not callable(getattr(data, item))]
pp.pprint(df_methods)
pp.pprint(df_attr)
Split data according to criterion, do numeric transformations, then recombine.
# Get all GDPs greater than the mean
mask_higher = data > data.mean()
# Count the number of time periods in which each country exceeds the mean
higher_count = mask_higher.aggregate('sum', axis=1)
# Create a normalized wealth-over-time score
wealth_score = higher_count / len(data.columns)
wealth_score
A DataFrame is a spreadsheet, but it is also a dictionary of columns.
data['gdpPercap_1962']
Add column to data frame
# Warningealth Score is a series
type(wealth_score)
data['normalized_wealth'] = wealth_score
Broadly, a trade-off between managing big code bases and making it easy to experiment. See: https://github.com/elliewix/Ways-Of-Installing-Python/blob/master/ways-of-installing.md#why-do-you-need-a-specific-tool
Python is an interactive interpreter (REPL)
python
Python is a command line program
# hello.py
print("Hello!")
python hello.py
(Optional) Python programs can accept command line arguments as inputs
sys.argv (https://docs.python.org/3/library/sys.html#sys.argv)argparse (https://docs.python.org/3/library/argparse.html)for loop to process files given a list of their namesimport pandas as pd
file_list = ['data/gapminder_gdp_africa.csv', 'data/gapminder_gdp_asia.csv']
for filename in file_list:
data = pd.read_csv(filename, index_col='country')
print(filename)
print(data.head(1))
Get a list of all the CSV files
import glob
glob.glob('data/*.csv')
In Unix, the term “globbing” means “matching a set of files with a pattern”. It uses shell expansion rules, not regular expressions, so there’s an upper limit to how flexible it can be. The most common patterns are:
Get a list of all the Gapminder CSV files
glob.glob('data/gapminder_*.csv')
Exclude the “all” CSV file
glob.glob('data/gapminder_[!all]*.csv')
for loop to process batches of filesdata_frames = []
for filename in glob.glob('data/gapminder_[!all]*.csv'):
data = pd.read_csv(filename)
data_frames.append(data)
all_data = pd.concat(data_frames)
print(all_data.shape)
Value of a variable
mass = 3
print(mass == 3)
print(mass > 5)
print(mass < 4)
Membership in a collection
primes = [2, 3, 5]
print(2 in primes)
print(7 in primes)
Truth of a collection Note that any() and all() evaluate each item using .__bool__() or .__len()__, which tells you whether an item is “truthy” or “falsey” (i.e. interpreted as being true or false).
my_list = [2.75, "green", 0]
print(any(my_list))
print(all(my_list))
(Optional) Understanding “truthy” and “falsey” values in Python (cf. https://stackoverflow.com/a/53198991) Every value in Python, regardless of type, is interpreted as being True except for the following values (which are interpreted as False). “Truthy” values satisfy if or while statements; “Falsey” values do not.
None and False.0, 0.0, 0j, Decimal(0), Fraction(0, 1)'', (), [], {}, set(), range(0)if statements to control whether or not a block of code is executedAn if statement (more properly called a conditional statement) controls whether some block of code is executed or not.
mass = 3.5
if mass > 3.0:
print(mass, 'is large')
mass = 2.0
if mass > 3.0:
print (mass, 'is large')
Structure is similar to a for statement:
if and ends with a colonelse can be used following an if. This allows us to specify an alternative to execute when the if branch isn’t taken.
if m > 3.0:
print(m, 'is large')
else:
print(m, 'is small')
elif to specify additional testsMay want to provide several alternative choices, each with its own test; use elif (short for “else if”) and a condition to specify these.
if m > 9.0:
print(m, 'is HUGE')
elif m > 3.0:
print(m, 'is large')
else:
print(m, 'is small')
if.else (which is the “catch all”).Python steps through the branches of the conditional in order, testing each in turn. Order matters! The following is wrong:
grade = 85
if grade >= 70:
print('grade is C')
elif grade >= 80:
print('grade is B')
elif grade >= 90:
print('grade is A')
and, or, and ParenthesesOften, you want some combination of things to be true. You can combine relations within a conditional using and and or. Continuing the example above, suppose you have:
mass = [ 3.54, 2.07, 9.22, 1.86, 1.71]
velocity = [10.00, 20.00, 30.00, 25.00, 20.00]
for m, v in zip(mass, velocity):
if m > 5 and v > 20:
print("Fast heavy object. Duck!")
elif m > 2 and m <= 5 and v <= 20:
print("Normal traffic")
elif m <= 2 and v <= 20:
print("Slow light object. Ignore it")
else:
print("Whoa! Something is up with the data. Check it")
zip()Conditionals are often used inside loops.
data_frames = []
for count, filename in enumerate(glob.glob('data/gapminder_[!all]*.csv')):
# Print every other filename
if count % 2 == 0:
print(count, filename)
data = pd.read_csv(filename)
data_frames.append(data)
all_data = pd.concat(data_frames)
print(all_data.shape)
Iterate through all of the CSV files in the data directory. Print the file name and file length for any file that is less than 30 lines long.
for filename in glob.glob('data/*.csv'):
data = pd.read_csv(filename)
if len(data) < 30:
print(filename, len(data))
Pathlib provides cross-platform path objects
from pathlib import Path
# Create Path objects
raw_path = Path("data")
processed_path = Path("data/processed")
print("Relative path:", raw_path)
print("Absolute path:", raw_path.absolute())
The file objects have methods that provide much better information about files and directories.
#Note the careful testing at each level of the code.
data_frames = []
if relative_path.exists():
for filename in raw_path.glob('gapminder_[!all]*.csv'):
if filename.is_file():
data = pd.read_csv(filename)
print(filename)
data_frames.append(data)
all_data = pd.concat(data_frames)
# Check for destination folder and create if it doesn't exist
if not processed_path.exists():
processed_path.mkdir()
all_data.to_csv(processed_path.joinpath("combined_data.csv"))
Pandas understands specific file types, but what if you need to work with a generic file?
with open("data/bouldercreek_09_2013.txt", "r") as infile:
lines = infile.readlines()
"bouldercreek_09_2013.txt" is the name of the fileinfile is a variable that refers to the file on disk.readlines() produces the file contents as a list of lines; each line is a string.
print(len(text))
print(type(text))
# View the first 10 lines
print(text[:10])
Compare the following:
# This displays the nicely-formatted document
print(lines[0])
# This shows the true nature of the string; you can see newlines (/n),
# tabs (/t), and other hidden characters
lines[0]
The file contains front matter that we can discard
tabular_lines = []
for line in lines:
if not line.startswith("#"):
tabular_lines.append(line)
Now the first line is tab-separated data. Note that the print statement prints the tabs instead of showing us the \t character.
tabular_lines[0]
outfile_name = "data/tabular_data.txt"
with open(outfile_name, "w") as outfile:
outfile.writelines(tabular_lines)
Strip trailing whitespace
stripped_line = tabular_lines[0].strip()
stripped_line
Split each line into a list based using the tabs.
split_line = stripped_line.split("\t")
split_line
Use a special-purpose library to create a correctly-formatted CSV file
import csv
outfile_name = "data/csv_data.csv"
with open(outfile_name, "w") as outfile:
writer = csv.writer(outfile)
for line in tabular_lines:
csv_line = line.strip().split("\t")
writer.writerow(csv_line)
You can initialize csv.reader and csv.writer with different “dialects” or with custom delimiters and quotechars; see https://docs.python.org/3/library/csv.html
infile_name = "data/bouldercreek_09_2013.txt"
outfile_name = "data/csv_data.csv"
with open(infile_name, "r") as infile, open(outfile_name, "w") as outfile:
writer = csv.writer(outfile)
for line in infile:
if not line.startswith("#"):
writer.writerow(line.strip().split("\t"))
Pandas has utilities for reading fixed-width files: https://pandas.pydata.org/docs/reference/api/pandas.read_fwf.html
Saving datasets with new-style string formatting
for i in datasets_list:
do_something(f'{i}.png'
def with a name, parameters, and a block of codedef print_greeting():
print('Hello!')
def, followed by the name of the function.print_greeting()
Positional arguments
def print_date(year, month, day):
joined = '/'.join([year, month, day])
print(joined)
print_date(1871, 3, 19)
(Optional) Keyword arguments
print_date(month=3, day=19, year=1871)
returnUse return ... to give a value back to the caller. return ends the function’s execution and returns you to the code that originally called the function.
def average(values):
"""Return average of values, or None if no values are supplied."""
if len(values) == 0:
return None
else:
return sum(values) / len(values)
a = average([1, 3, 4])
print(a)
You should explicitly handle common problems:
print(average([]))
Notes:
return can occur anywhere in the function, but functions are easier to understand if return occurs:
Write a function that takes line as an input and returns the information required by writer.writerow().
def norm_data(data):
"""Add a Z score column to each data set."""
# Calculate z scores for all elements
z = (data - data.mean())/data.std()
# Get the mean z score for each country
mean_z = z.mean(axis=1)
# Group countries into "wealthy" (z > 0) and "not wealthy" (z <= 0)
z_bool = mean_z > 0
return mean_z, z_bool
data = pd.read_csv("data/gapminder_gdp_europe.csv", index_col = "country")
mean_z, z_bool = norm_data(data)
# If you need to drop the contintent column
# mean_z, z_bool = norm_data(data.drop("continent", axis=1))
for filename in glob.glob('data/gapminder_*.csv'):
# Print a status message
print("Current file:", filename)
# Read the data into a DataFrame and modify it
data = pd.read_csv(filename, index_col = "country")
mean_z, z_bool = norm_data(data)
# Append to DataFrame
data["mean_z"] = mean_z
data["wealthy"] = z_bool
# Generate an output file name
parts = filename.split(".csv")
newfile = ''.join([parts[0], "_normed.csv"])
data.to_csv(newfile)
https://matplotlib.org/stable/gallery/mplot3d/lorenz_attractor.html
if statements to control whether or not a block of code is executedAn if statement (more properly called a conditional statement) controls whether some block of code is executed or not.
mass = 3.54
if mass > 3.0:
print(mass, 'is large')
mass = 2.07
if mass > 3.0:
print (mass, 'is large')
Structure is similar to a for statement:
if and ends with a colonNot much point using a conditional when we know the value (as above), but useful when we have a collection to process.
masses = [3.54, 2.07, 9.22, 1.86, 1.71]
for m in masses:
if m > 3.0:
print(m, 'is large')
else can be used following an if. This allows us to specify an alternative to execute when the if branch isn’t taken.
masses = [3.54, 2.07, 9.22, 1.86, 1.71]
for m in masses:
if m > 3.0:
print(m, 'is large')
else:
print(m, 'is small')
elif to specify additional testsMay want to provide several alternative choices, each with its own test; use elif (short for “else if”) and a condition to specify these.
masses = [3.54, 2.07, 9.22, 1.86, 1.71]
for m in masses:
if m > 9.0:
print(m, 'is HUGE')
elif m > 3.0:
print(m, 'is large')
else:
print(m, 'is small')
if.else (which is the “catch all”).Python steps through the branches of the conditional in order, testing each in turn. Order matters! The following is wrong:
grade = 85
if grade >= 70:
print('grade is C')
elif grade >= 80:
print('grade is B')
elif grade >= 90:
print('grade is A')
velocity = 10.0
for i in range(5): # execute the loop 5 times
print(i, ':', velocity)
if velocity > 20.0:
velocity = velocity - 5.0
else:
velocity = velocity + 10.0
print('final velocity:', velocity)
and, or, and Parentheses (optional)Often, you want some combination of things to be true. You can combine relations within a conditional using and and or. Continuing the example above, suppose you have:
mass = [ 3.54, 2.07, 9.22, 1.86, 1.71]
velocity = [10.00, 20.00, 30.00, 25.00, 20.00]
i = 0
for i in range(5):
if mass[i] > 5 and velocity[i] > 20:
print("Fast heavy object. Duck!")
elif mass[i] > 2 and mass[i] <= 5 and velocity[i] <= 20:
print("Normal traffic")
elif mass[i] <= 2 and velocity[i] <= 20:
print("Slow light object. Ignore it")
else:
print("Whoa! Something is up with the data. Check it")
zip()import matplotlib.pyplot as plt
fig, ax = plt.subplots()
time = [0, 1, 2, 3]
position = [0, 100, 200, 300]
ax.plot(time, position)
type(fig)
print(type(fig))
print(type(ax))
Show figure inline (Jupyter Lab default)
fig
Show figure in a separate window (command line default)
fig.show()
Show figure in a separate window from Jupyter Lab. You may need to specify a different “backend” parameter for matplotlib.use() depending on your exact setup: https://matplotlib.org/stable/tutorials/introductory/usage.html#the-builtin-backends
import matplotlib
matplotlib.use('TkAgg')
fig.show()
Create mock data
import numpy as np
y = np.random.random(10) # outputs an array of 10 random numbers between 0 and 1
x = np.arange(1980,1990,1) # generates an ordered array of numbers from 1980 to 1989
# Check that x and y contain the same number of values
assert len(x) == len(y)
Inspect our data
print("x:", x)
print("y:", y)
Create the basic plot
# Convert y axis into a percentage
y = y * 100
# Draw plot
fig, ax = plt.subplots()
ax.plot(x, y)
Show available styles
# What are the global styles?
plt.style.available
# Set a global figure style
plt.style.use("dark_background")
# The style is only applied to new figures, not pre-existing figures
fig
# Re-creating the figure applies the new style
fig, ax = plt.subplots()
ax.plot(x, y)
Customize the graph In principle, nearly every element on a Matplotlib figure is independently modifiable.
# Set figure size
fig, ax = plt.subplots(figsize=(8,6))
# Set line attributes
ax.plot(x, y, color='darkorange', linewidth=2, marker='o')
# Add title and labels
ax.set_title("Percent Change in Stock X", fontsize=22, fontweight='bold')
ax.set_xlabel(" Years ", fontsize=20, fontweight='bold')
ax.set_ylabel(" % change ", fontsize=20, fontweight='bold')
# Adjust the tick labels
ax.tick_params(axis='both', which='major', labelsize=18)
# Add a grid
ax.grid(True)
Save your figure
fig.savefig("mygraph_dark.png", dpi=300)
In this example, plot GDP over time for multiple countries.
Import data
import pandas as pd
data = pd.read_csv('data/gapminder_gdp_europe.csv', index_col='country')
# Inspect our data
data.head(3)
Transform column headers into an ordinal scale
(Optional) Original column names are object (i.e. string) data
data.columns
Strip off non-numeric portion of each column title
years = data.columns.str.strip('gdpPercap_')
years
Convert years strings into integers and replace original data frame column headers
data.columns = years.astype(int)
Extract rows from the DataFrame
x_years = data.columns
y_austria = data.loc['Austria']
y_bulgaria = data.loc['Bulgaria']
Create the plot object
# Change global background back to default
plt.style.use("default")
# Create GDP figure
fig, ax = plt.subplots(figsize=(8,6))
# Create GDP plot
ax.plot(x_years, y_austria, label='Austria', color='darkgreen', linewidth=2, marker='x')
ax.plot(x_years, y_bulgaria, label='Bulgaria', color='maroon', linewidth=2, marker='o')
# Decorate the plot
ax.legend(fontsize=16, loc='upper center') #automatically uses labels
ax.set_title("GDP of Austria vs Bulgaria", fontsize=22, fontweight='bold')
ax.set_xlabel("Years", fontsize=20, fontweight='bold')
ax.set_ylabel("GDP", fontsize=20, fontweight='bold')
Don’t do this.
The basic plot syntax
ax = data.loc['Austria'].plot()
fig = ax.get_figure()
fig
Decorate your Pandas plot
ax = data.loc['Austria'].plot(figsize=(8,6), color='darkgreen', linewidth=2, marker='*')
ax.set_title("GDP of Austria", fontsize=22, fontweight='bold')
ax.set_xlabel("Years",fontsize=20, fontweight='bold' )
ax.set_ylabel("GDP",fontsize=20, fontweight='bold' )
fig = ax.get_figure()
fig
Overlaying multiple plots on the same figure with Pandas. This is super unintuitive.
# Create an Axes object with the Austria data
ax = data.loc['Austria'].plot(figsize=(8,6), color='darkgreen', linewidth=2, marker='*')
print("Austria graph", id(ax))
# Overlay the Bulgaria data on the same Axes object
ax = data.loc['Bulgaria'].plot(color='maroon', linewidth=2, marker='o')
print("Bulgaria graph", id(ax))
The equivalent Matplotlib plot (optional)
# extract the x and y values from dataframe
x_years = data.columns
y_gdp = data.loc['Austria']
# Create the plot
fig, ax = plt.subplots(figsize=(8,6))
ax.plot(x_years, y_gdp, color='darkgreen', linewidth=2, marker='x')
# etc.
## Visualize the same data using a scatterplot
plt.style.use('ggplot')
# Create a scatter plot
fig, ax = plt.subplots(figsize=(8,6))
ax.scatter(y_austria, y_bulgaria, color='blue', linewidth=2, marker='o')
# Decorate the plot
ax.set_title("GDP of Austria vs Bulgaria", fontsize=22, fontweight='bold')
ax.set_xlabel("GDP of Austria",fontsize=20, fontweight='bold' )
ax.set_ylabel("GDP of Bulgaria",fontsize=20, fontweight='bold' )
Seaborn is a set of high-level pre-sets for Matplotlib.
# Import the Seaborn library
import seaborn as sns
ax = sns.lineplot(data=data.T, legend=False, dashes=False)
Let’s make a poster!
Import Iris data set https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv
iris = pd.read_csv("../data/iris.csv")
iris.head()
Create a basic scatter plot
ax = sns.scatterplot(data=iris, x='sepal_length',y='petal_length')
Change plotting theme
plt.style.use("dark_background")
# Fix grid if necessary
#plt.rcParams["axes.grid"] = False
# Make everything visible at a distance
sns.set_context('poster')
# Color by species
ax = sns.scatterplot(data=iris, x='sepal_length', y='petal_length', hue='species',
palette='colorblind', size='petal_width')
# Place legend
ax.legend(bbox_to_anchor=(2,1))
loc vs. bbox_to_anchor in the legend documentation: https://matplotlib.org/stable/api/legend_api.htmlThe Seaborn plot uses Matplotlib under the hood
# Set the figure size
fig = ax.get_figure()
fig.set_size_inches(8,6)
fig
Add styling to individual points
ax = sns.scatterplot(data=iris, x='sepal_length', y='petal_length', hue='species', palette='colorblind', style='species')
Prettify column names
words = [' '.join(i) for i in iris.columns.str.split('_')]
iris.columns = words
Make a regression plot
# Color by species, size by petal width
ax = sns.regplot(data=iris, x='sepal_length', y='petal_length', scatter=True,
scatter_kws={'color':'white'})
Bar Plot
ax = sns.barplot(data=iris, x='species', y='sepal_width', palette='colorblind')
Add custom parameters
# Error bars show standard deviation
ax = sns.barplot(data=iris, x='species', y='sepal_width', ci='sd', edgecolor='black')
(Optional) count plot counts the records in each category
ax = sns.countplot(data=iris, x='species', palette='colorblind')
Histogram of overall data set
ax = sns.histplot(data=iris, x='petal_length', kde=True)
Histogram of data decomposed by category
ax = sns.histplot(data=iris, x='petal_length', hue='species', palette='Set2')
Create multiple subplots to compare binning strategies
# This generates 3 subplots (ncols=3) on the same figure
fig, axes = plt.subplots(figsize=(12,4), nrows=1, ncols=3)
# Note that we can use Seaborn to draw on our Matplotlib figure
sns.histplot(data=iris,x='petal_length', bins=5, ax=axes[0], color='#f5a142')
sns.histplot(data=iris,x='petal_length', bins=10, ax=axes[1], color='maroon')
sns.histplot(data=iris,x='petal_length', bins=15, ax=axes[2], color='darkmagenta')
Box plot
ax = sns.boxplot(data=iris, x='species', y='petal_length')
Swarm plot
ax = sns.swarmplot(data=iris,x='species', y='petal_length', hue='species', palette='Set1')
ax.legend(loc='upper left', fontsize=16)
ax.tick_params(axis='x', labelrotation = 45)
This gives us a format warning.
Strip plot
ax = sns.swarmplot(data=iris,x='species', y='petal_length', hue='species', palette='Set1')
ax.legend(loc='upper left', fontsize=16)
ax.tick_params(axis='x', labelrotation = 45)
Overlapping plots
ax = sns.boxplot(data=iris, x='species', y='petal_length')
sns.stripplot(data=iris, x='species', y='petal_length', ax=ax, palette='Set1')
plt vs axeslegend() vs get legend handles and patchesobject.set_field(value) usage is taken from Java, which was popular in 2003 when Matplotlib was developing its object-oriented syntaxobject.get_field(value)object.field = value. However, the Matplotlib getters and setters do a lot of internal bookkeeping, so if you try to set field values directly you will get errors. For example, compare ax.get_ylabel() with ax.yaxis.label.with open('pettigrew_letters_ORIGINAL.txt', 'r') as file_in:
text = file_in.read()
print(len(text))
Compare the following:
# This displays the nicely-formatted document
print(text[:300])
# This shows the true nature of the string; you can see newlines (/n),
# tabs (/t), and other hidden characters
text[:300]
.read() produces the file contents as one stringtype(text)
.readlines() produces the file contents as a list of lines; each line is a stringwith open('pettigrew_letters_ORIGINAL.txt', 'r') as file_in:
text = file_in.readlines()
print(len(text))
print(type(text))
# View the first 10 lines
text[:10]
manifest_list = text[14:159]
Demonstrate string tests with manifest_list:
# Raw text
for location in manifest_list[:10]:
print(location)
# Remove extra whitespace
for location in manifest_list[:10]:
print(location.strip())
# Test whether the cleaned line starts with 'Box '
for location in manifest_list[:10]:
stripped_line = location.strip()
print(stripped_line.startswith('Box '))
# Test whether the cleaned line starts with 'box '
for location in manifest_list[:10]:
stripped_line = location.strip()
print(stripped_line.startswith('box '))
letters = text[162:]
for line in letters[:25]:
# Create a variables to hold current line and truth value of is_box
stripped_line = line.strip()
is_box = stripped_line.startswith('Box ')
if is_box == True:
print(stripped_line)
# If the line is empty, don't print anything
elif stripped_line == '\n':
continue
# Indent non-Box lines
else:
print('---', stripped_line)
print() statements so that we can see what’s happeningletter_locations = []
for line in letters:
stripped_line = line.strip()
is_box = stripped_line.startswith("Box ")
if is_box == True:
letter_locations.append(stripped_line)
print('Items in manifest:', len(manifest_list))
print('Letters:', len(letter_locations))
Explicitly handle common errors, rather than waiting for your code to blow up.
def average(values):
"Return average of values, or None if no values are supplied."
if len(values) == 0:
return None
return sum(values) / len(values)
print(average([3, 4, 5])) # Prints expected output
print(average([])) # Explicitly handles possible divide-by-zero error
print(average(4)) # Unhandled exception
def average(values):
"Return average of values, or an informative error if bad values are supplied."
try:
return sum(values) / len(values)
except ZeroDivisionError as err:
return err
except TypeError as err:
return err
print(average([3, 4, 5]))
print(average(4))
print(average([]))
from timeit import time
import cProfile
import pstats
def my_fun(val):
# Get 1st timestamp
t1 = time.time()
# do work
# Get 2nd timestamp
t2 = time.time()
print(round(t2 - t1, 3))
# Run the function with the profiler and collect stats
cProfile.run('my_fun(val)', 'dumpstats')
s = pstats.Stats('dumpstats')
with open('pettigrew_letters_ORIGINAL.txt', 'r') as file_in:
for line in file_in:
# Do stuff to current line
pass
import sqlite3
conn = sqlite3.connect('my_database_name.db')
with conn:
c = conn.execute("SELECT column_name FROM table_name WHERE criterion")
results = c.fetchall()
c.close
# Do stuff with `results`