- Explain the structure of a Series and a DataFrame (values, index, columns, dtypes)
- Read a CSV and get a first look at the data with
head,info,describeandvalue_counts - Select and sort rows and columns with
loc,iloc, masks andquery - Create new columns in a vectorized way without chained assignment
Aysel runs a small online stationery shop. Her sales are in an Excel sheet: product, category, price, stock and units sold. To answer questions like “which category brings the most revenue?” or “what needs to be reordered?” across hundreds of rows, there is the pandas library. It adds column names, row labels and hundreds of ready-made table operations on top of NumPy arrays.
Series and DataFrame
A labelled one-dimensional array: values (a NumPy array) plus a matching index (labels). It has a name (name) and a type (dtype).
A two-dimensional table: a collection of Series (columns) that share one index. Each column can have its own type — text, integer, float, date.
In a Series you can reach an element by label (s['Pen']) or by position (s.iloc[0]). Arithmetic is vectorized just as in NumPy. Below we add 18% VAT to the prices:
import pandas as pd
prices = pd.Series([1.5, 0.6, 35.0, 49.9],
index=['Notebook', 'Pen', 'Backpack', 'Headphones'],
name='price')
print(prices)
print(prices['Pen'], prices.iloc[-1])
print(prices[prices > 10].index.tolist())
print((prices * 1.18).round(2).tolist())▸ Expected output
Notebook 1.5 Pen 0.6 Backpack 35.0 Headphones 49.9 Name: price, dtype: float64 0.6 49.9 ['Backpack', 'Headphones'] [1.77, 0.71, 41.3, 58.88]
The simplest way to build a DataFrame is from a dictionary: the keys are the column names and the values are the column contents. shape is a (rows, columns) tuple, and dtypes shows the type of every column. In pandas 3 text columns have the type str (in older versions you will see object).
import pandas as pd
df = pd.DataFrame({
'name': ['Aysel', 'Murad', 'Leyla'],
'age': [19, 21, 20],
'city': ['Baku', 'Ganja', 'Baku'],
})
print(df)
print(df.shape, df.columns.tolist())
print(df.dtypes)▸ Expected output
name age city 0 Aysel 19 Baku 1 Murad 21 Ganja 2 Leyla 20 Baku (3, 3) ['name', 'age', 'city'] name str age int64 city str dtype: object
Reading a CSV and a first look at the data
In real work a table is read from a file: pd.read_csv('sales.csv'). In the browser there are no files, so we turn text into a “file” with io.StringIO — read_csv does not notice the difference. Each code block runs on its own, so we create the table again in every block. The first step is always the same: head() shows the first rows, and info() shows the columns, their types and the number of non-empty values. Below we print the same information piece by piece: shape, dtypes and count().
import io
import pandas as pd
data = '''product,category,price,stock,sold
Notebook,Stationery,1.5,120,340
Pen,Stationery,0.6,300,820
Backpack,Accessories,35.0,25,48
Headphones,Electronics,49.9,15,37
USB cable,Electronics,6.5,80,150
Mug,Home,8.0,40,95
Desk lamp,Home,27.5,12,20'''
df = pd.read_csv(io.StringIO(data))
print(df.head(3))
print(df.shape)
print(df.dtypes)
print(df.count().tolist())▸ Expected output
product category price stock sold 0 Notebook Stationery 1.5 120 340 1 Pen Stationery 0.6 300 820 2 Backpack Accessories 35.0 25 48 (7, 5) product str category str price float64 stock int64 sold int64 dtype: object [7, 7, 7, 7, 7]
read_csv detected the types by itself: numbers became int64 and float64, text became str. count() counts the non-empty values in each column — there are no gaps here.describe() gives, for every numeric column, the count, mean, standard deviation (dividing by n − 1), minimum, quartiles and maximum in one table. For a categorical column, value_counts() counts how many times each value appears.
import io
import pandas as pd
data = '''product,category,price,stock,sold
Notebook,Stationery,1.5,120,340
Pen,Stationery,0.6,300,820
Backpack,Accessories,35.0,25,48
Headphones,Electronics,49.9,15,37
USB cable,Electronics,6.5,80,150
Mug,Home,8.0,40,95
Desk lamp,Home,27.5,12,20'''
df = pd.read_csv(io.StringIO(data))
print(df.describe().round(2))
print(df['category'].value_counts())▸ Expected output
price stock sold count 7.00 7.00 7.00 mean 18.43 84.57 215.71 std 19.16 102.74 288.06 min 0.60 12.00 20.00 25% 4.00 20.00 42.50 50% 8.00 40.00 95.00 75% 31.25 100.00 245.00 max 49.90 300.00 820.00 category Stationery 2 Electronics 2 Home 2 Accessories 1 Name: count, dtype: int64
- qthe level: 0.25 (first quartile), 0.5 (median), 0.75 (third quartile)
- x₍ₖ₎the k-th value after sorting in ascending order (counting from 0)
- hthe “fractional position” in the sorted data
By default pandas and NumPy compute quantiles by linear interpolation between two neighbouring values.
Compute the 25% and 75% quartiles of the price column by hand and compare them with the describe() result.
Show solutionHide solution
q = 0.25: h = 6 · 0.25 = 1.5 → halfway between x₍₁₎ = 1.5 and x₍₂₎ = 6.5: 1.5 + 0.5 · 5 = 4.0.
q = 0.75: h = 6 · 0.75 = 4.5 → 27.5 + 0.5 · 7.5 = 31.25.
Median: h = 3 → x₍₃₎ = 8.0. All three match
describe(). The mean (18.43) is much larger than the median — a few expensive products pull the mean up.Selecting: columns, loc and iloc
| Syntax | What it returns |
|---|---|
df['price'] | one column — a Series |
df[['product', 'price']] | several columns — a DataFrame |
df.loc[row_labels, col_labels] | selection by labels; the last label of a slice is included |
df.iloc[row_pos, col_pos] | selection by positions; the end of a slice is excluded |
import io
import pandas as pd
data = '''product,category,price,stock,sold
Notebook,Stationery,1.5,120,340
Pen,Stationery,0.6,300,820
Backpack,Accessories,35.0,25,48
Headphones,Electronics,49.9,15,37
USB cable,Electronics,6.5,80,150
Mug,Home,8.0,40,95
Desk lamp,Home,27.5,12,20'''
df = pd.read_csv(io.StringIO(data))
print(df['price'].head(3).tolist())
print(df.loc[2, 'product'], '|', df.iloc[0, 2])
print(df.loc[1:3, 'product':'price'])
print(df.iloc[1:3, 0:2])
df = df.set_index('product')
print(df.loc['Mug', 'price'])▸ Expected output
[1.5, 0.6, 35.0]
Backpack | 1.5
product category price
1 Pen Stationery 0.6
2 Backpack Accessories 35.0
3 Headphones Electronics 49.9
product category
1 Pen Stationery
2 Backpack Accessories
8.0loc[1:3] returned three rows (1, 2, 3), while iloc[1:3] returned two (1, 2). After set_index('product') you can reach rows by product name.Filtering and sorting
We filter rows with boolean masks, just as in NumPy: conditions are combined with &, | and ~ and put in parentheses. isin checks whether a value is in a list, and query lets you write the condition as a string. sort_values sorts by a column, and nlargest returns the n largest values directly.
import io
import pandas as pd
data = '''product,category,price,stock,sold
Notebook,Stationery,1.5,120,340
Pen,Stationery,0.6,300,820
Backpack,Accessories,35.0,25,48
Headphones,Electronics,49.9,15,37
USB cable,Electronics,6.5,80,150
Mug,Home,8.0,40,95
Desk lamp,Home,27.5,12,20'''
df = pd.read_csv(io.StringIO(data))
cheap = df[df['price'] < 10]
print(cheap[['product', 'price']])
print(df[(df['category'] == 'Electronics') & (df['stock'] < 50)]['product'].tolist())
print(df[df['category'].isin(['Home', 'Accessories'])]['product'].tolist())
print(df.query('price > 20 and sold > 30')[['product', 'sold']])
print(df.sort_values('sold', ascending=False).head(3)[['product', 'sold']])
print(df.nlargest(2, 'price')[['product', 'price']])▸ Expected output
product price
0 Notebook 1.5
1 Pen 0.6
4 USB cable 6.5
5 Mug 8.0
['Headphones']
['Backpack', 'Mug', 'Desk lamp']
product sold
2 Backpack 48
3 Headphones 37
product sold
1 Pen 820
0 Notebook 340
4 USB cable 150
product price
3 Headphones 49.9
2 Backpack 35.0New columns
A new column is created by assignment: df['revenue'] = df['price'] * df['sold'] — no loop, the calculation runs over the whole column. For a conditional value use np.where; to change only some rows use df.loc[mask, 'column'] = value. rename and drop return a new DataFrame, so we assign the result back to the variable.
import io
import numpy as np
import pandas as pd
data = '''product,category,price,stock,sold
Notebook,Stationery,1.5,120,340
Pen,Stationery,0.6,300,820
Backpack,Accessories,35.0,25,48
Headphones,Electronics,49.9,15,37
USB cable,Electronics,6.5,80,150
Mug,Home,8.0,40,95
Desk lamp,Home,27.5,12,20'''
df = pd.read_csv(io.StringIO(data))
df['revenue'] = (df['price'] * df['sold']).round(2)
df['band'] = np.where(df['price'] >= 20, 'high', 'low')
df['status'] = 'ok'
df.loc[df['stock'] < 20, 'status'] = 'reorder'
df = df.rename(columns={'sold': 'units'}).drop(columns=['category'])
print(df)
print(f"Total revenue: {df['revenue'].sum():.2f} AZN")▸ Expected output
product price stock units revenue band status 0 Notebook 1.5 120 340 510.0 low ok 1 Pen 0.6 300 820 492.0 low ok 2 Backpack 35.0 25 48 1680.0 high ok 3 Headphones 49.9 15 37 1846.3 high reorder 4 USB cable 6.5 80 150 975.0 low ok 5 Mug 8.0 40 95 760.0 low ok 6 Desk lamp 27.5 12 20 550.0 high reorder Total revenue: 6813.30 AZN
According to the table above, what percentage of the total revenue comes from the Electronics category? Compute it by hand and write the pandas expression.
Show solutionHide solution
Total revenue is 6813.3 manat.
Share = 2821.3 / 6813.3 ≈ 0.414, that is about 41.4%.
pandas:
df.loc[df['category'] == 'Electronics', 'revenue'].sum() / df['revenue'].sum() (compute it before the drop, while the category column still exists).pandas is an everyday tool for analysts, economists, banks, telecom companies and research labs: reports, sales analysis, survey results, sensor logs. In the next lesson we will handle missing values, grouping, joining tables and dates.
Read the CSV into a DataFrame. 1) Print, as a list, the names of students with a score of 80 or more, sorted by score in descending order; 2) print the mean score of all students rounded to 1 decimal place.
import io
import pandas as pd
data = '''name,group,score
Aysel,A,91
Murad,B,76
Leyla,A,85
Elvin,B,64
Nigar,A,78
Tural,B,88'''
df = pd.read_csv(io.StringIO(data))
# 1) names with score >= 80, highest first
# 2) mean score, 1 decimal▸ Expected output
['Aysel', 'Tural', 'Leyla'] 80.3
Add a column total = price * qty to the café's orders table. Then print on separate lines: 1) the name of the item with the largest total; 2) the list of items whose total is greater than 25; 3) the sum of all total values.
import pandas as pd
orders = pd.DataFrame({
'item': ['tea', 'coffee', 'cake', 'juice'],
'price': [2.5, 4.0, 5.5, 3.0],
'qty': [12, 7, 4, 9],
})
# add the total column, then print the three results▸ Expected output
tea ['tea', 'coffee', 'juice'] 107.0
Key points
- A Series is a labelled one-dimensional array; a DataFrame is a table of Series that share an index.
- A first look at new data:
head(),info(),describe(),value_counts(). locworks with labels and includes the end of a slice;ilocworks with positions and excludes it.- Filter with masks (
&,|,~),isinandquery; sort withsort_valuesandnlargest. - To change some rows, write
df.loc[mask, 'column'] = valueand avoid chained assignment.
Check yourself
10 questions. Every correct answer earns XP.
df.loc[2:4] return?