Series and DataFrame
In short: In pandas a Series is a column with labels, and a DataFrame is a table of such columns; the index carries the row names and lines up the arithmetic by them.
Problem
“How much money sits in the stockroom?”
The manager asks how much the stock in the stockroom is worth. The answer is price times stock for each product, then a sum. Next they will want to see the number for one product, by name.
You can already work it out with NumPy. But in the lesson on summaries argmax returned 22, a position, and you had to take the name from a separate names array. If you sort one array and forget the other, the names and the numbers drift apart and nobody notices.
pandas keeps a label next to every number: which product it is. In this lesson you will see how.
The idea
A column with labels: Series
A Series is a NumPy array with a label next to each value. The labels are called the index. Here are three prices with the product names as the index:
prices['White cheese'] gives 7.9: the value is taken by label, not by place. prices.index shows the labels, and to_numpy() gives the bare numbers as an array.
The idea
A table of columns: DataFrame
A DataFrame is several Series with a shared index, side by side. Each column has a name and its own type. You saw it in the first look; now look at it piece by piece:
columnsare the names of the columns: 6 of them.indexis aRangeIndexfrom 0 to 24:read_csvnumbers the rows from 0 unless you tell it otherwise. Theidcolumn is a different thing and starts at 1.dtypesis the type of each column.int64andfloat64are numbers,stris text.
See it
A column is a Series
You take a column with its name in square brackets, as in the first look. The result is a Series:
On the left is the index of the table (0, 1, 2), and at the bottom is the name of the column (Name: price) and its type. Everything you can do with a Series, you can do with every column.
The idea
Names in the index: set_index
The numbers 0 to 24 tell the manager nothing. set_index('name') makes the name column the index: the product names become the labels of the rows.
Now the stock of coffee is by_name['stock']['Coffee beans']: 15 packs. And the most expensive product is an answer with a name:
argmax()still gives the position: 22.idxmax()gives the label of the largest value: “Knife set”. There is alsoidxmin().
The labels travel with the numbers: whatever you do with a Series, the name stays next to the price.
The idea
And back: reset_index
set_index does not change products; it returns a new table. The name column is now its index, so there are 5 columns instead of 6. reset_index() turns the index back into an ordinary column and numbers the rows from 0 again:
What goes in the index depends on the question. For “by name” you put the names there; for a plain table that you will write to a file you turn them back into a column.
Try it
The cheapest product
Which product is the cheapest? Put the names in the index (by_name), and store in cheapest the name of the product with the smallest price.
Solution
import pandas as pd
products = pd.read_csv('products.csv')
by_name = products.set_index('name')
cheapest = by_name['price'].idxmin()
print(cheapest)by_name = products.set_index('name') and cheapest = by_name['price'].idxmin() gives “Butter roll” (€0.80). argmin() would return 2: a position, from which you still have to look up the name. Without set_index, idxmin() also gives 2, because then the labels are the row numbers.
See it
The index lines up the arithmetic
The supplier sends a new price list. The products are the same, but in a different order. With NumPy arrays you would subtract the first from the first and the second from the second. With Series, pandas subtracts the values with the same label, wherever they are:
With Series, coffee went up by €1, bread by €0.10 and cheese by €0.50. With the arrays the first number is €13.70: the new price of coffee minus the old price of bread. The last one is -€12.30: milk minus coffee. The numbers look perfectly serious and they are wrong.
This is called alignment by index. The result is ordered by label, alphabetically.
See it
A label that is missing
If a label is in only one of the Series, pandas has nothing to compute it with:
White cheese is only in this week, so its difference is NaN. That is honest: we do not know how much cheese we sold last week. You saw in summaries that missing is not zero, and pandas keeps to that.
More precisely, if you are curious
If you know that missing means zero (a product we did not sell last week), say so explicitly: this_week.sub(last_week, fill_value=0). That way the decision is visible in the code, not hidden. Alignment applies to +, -, *, / and to comparisons between two Series.
Try it
What went up
The two price lists are in a different order. Store in change how much the price of each product changed (new minus old), rounded to 2 places, with the product names in the index.
Solution
import pandas as pd
old_prices = pd.Series([1.2, 1.6, 7.9, 13.9], index=['White bread', 'Fresh milk', 'White cheese', 'Coffee beans'])
new_prices = pd.Series([14.9, 1.3, 8.4, 1.6], index=['Coffee beans', 'White bread', 'White cheese', 'Fresh milk'])
change = (new_prices - old_prices).round(2)
print(change)change = (new_prices - old_prices).round(2): pandas finds each product in both lists. Coffee is +€1.00, cheese +€0.50, bread +€0.10, and milk is unchanged. With to_numpy() the numbers are subtracted by place: the first is €13.70, new coffee minus old bread.
Exercise
The stock in the stockroom
Answer the manager. Put the product names in the index. Store in stock_value the value of the stock of each product (price times stock), in total its sum, and in real_total the sum without the gift card, rounded to 2 places.
Solution
import pandas as pd
products = pd.read_csv('products.csv')
by_name = products.set_index('name')
stock_value = by_name['price'] * by_name['stock']
total = stock_value.sum()
real_total = round(total - stock_value['Gift card'], 2)
print(round(total, 2), real_total)stock_value = by_name['price'] * by_name['stock'] is a Series with the names in the index. total is €22,542.30, but €19,980 of it is the gift card: stock 999 at €20. From the first look you know that 999 means “no limit”, not 999 cards on the shelf. real_total = total - stock_value['Gift card'] gives €2,562.30: that is the stock. With the names in the index you take the card out by name, not by the number 24, which moves as soon as the table is sorted another way.
Test
Check yourself
Related lessons
Section titled "Related lessons"- Builds on: The first table · The NumPy array · Vectorization · Summaries and missing values
- Continues in: Selecting: [], loc and iloc · Filters and sorting · New columns · Types and missing values
- Tasks on this topic: Tables with pandas