Advanced Data Analysis using Numpy & Pandas
Duration: 45 min
Advanced Data Analysis using Numpy & Pandas
Duration: 45 min
GroupBy Operations
GroupBy is one of the most powerful tools in Pandas. It allows you to split your data into groups based on one or more columns, apply a function to each group, and combine the results. This is the foundation of exploratory data analysis.
import pandas as pd
import numpy as npCreate sample dataset
df = pd.DataFrame({
'department': ['Sales', 'Sales', 'IT', 'IT', 'HR', 'HR', 'HR'],
'employee': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace'],
'salary': [50000, 55000, 70000, 75000, 45000, 48000, 50000],
'year': [2022, 2023, 2022, 2023, 2022, 2023, 2023]
})Group by single column
grouped = df.groupby('department')
print(grouped['salary'].mean())
department
HR 47666.666667
IT 72500.000000
Sales 52500.000000
Group by multiple columns
grouped = df.groupby(['department', 'year'])
print(grouped['salary'].sum())Apply multiple aggregations
agg_result = df.groupby('department').agg({
'salary': ['mean', 'sum', 'count'],
'employee': 'count'
})
print(agg_result)Custom aggregation with apply()
def salary_range(group):
return group['salary'].max() - group['salary'].min()result = df.groupby('department')['salary'].apply(salary_range)
print(result)
Filter groups
large_depts = df.groupby('department').filter(lambda x: len(x) > 2)
print(large_depts)
Pivot Tables
Pivot tables reshape data to summarize and compare. They're essential for creating crosstabs and multi-dimensional summaries.
import pandas as pddf = pd.DataFrame({
'date': ['2023-01-01', '2023-01-01', '2023-01-02', '2023-01-02',
'2023-01-03', '2023-01-03'],
'product': ['A', 'B', 'A', 'B', 'A', 'B'],
'sales': [100, 150, 120, 140, 110, 160],
'region': ['North', 'North', 'South', 'North', 'South', 'South']
})
Basic pivot table
pivot = df.pivot_table(values='sales', index='date', columns='product', aggfunc='sum')
print(pivot)
product A B
date
2023-01-01 100 150
2023-01-02 120 140
2023-01-03 110 160
Pivot with multiple index levels
pivot2 = df.pivot_table(values='sales', index=['date', 'region'],
columns='product', aggfunc='mean')Pivot with multiple aggregations
pivot3 = df.pivot_table(values='sales', index='date', columns='product',
aggfunc=['sum', 'mean', 'count'])unstack (convert index to columns)
unstacked = df.set_index(['date', 'product'])['sales'].unstack()
print(unstacked)stack (convert columns to index)
stacked = pivot.stack()
print(stacked)
Merge and Join Operations
Merging combines datasets based on common columns or indices. It's analogous to SQL joins.
import pandas as pdSample datasets
df1 = pd.DataFrame({
'emp_id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie'],
'dept_id': [10, 20, 10]
})df2 = pd.DataFrame({
'dept_id': [10, 20, 30],
'dept_name': ['Sales', 'IT', 'HR']
})
Inner join (only matching rows)
result = pd.merge(df1, df2, on='dept_id', how='inner')
print(result)
emp_id name dept_id dept_name
0 1 Alice 10 Sales
1 2 Bob 20 IT
Left join (all rows from left df)
result = pd.merge(df1, df2, on='dept_id', how='left')Right join (all rows from right df)
result = pd.merge(df1, df2, on='dept_id', how='right')Outer join (all rows from both)
result = pd.merge(df1, df2, on='dept_id', how='outer')Merge on different column names
df3 = pd.DataFrame({'department_id': [10, 20], 'budget': [100000, 150000]})
result = pd.merge(df1, df3, left_on='dept_id', right_on='department_id')Concatenate dataframes (stack vertically)
df_concat = pd.concat([df1, df1], ignore_index=True)
Window Functions and Rolling Operations
Window functions compute values over a moving window of data, useful for time series analysis:
import pandas as pddf = pd.DataFrame({
'date': pd.date_range('2023-01-01', periods=10),
'price': [100, 102, 101, 105, 103, 108, 110, 107, 112, 115],
'volume': [1000, 1200, 950, 1500, 1100, 1400, 1600, 1300, 1800, 1900]
})
Rolling mean (moving average)
df['rolling_mean'] = df['price'].rolling(window=3).mean()
print(df[['date', 'price', 'rolling_mean']])Rolling sum
df['rolling_sum'] = df['volume'].rolling(window=3).sum()Expanding window (cumulative)
df['cumulative_max'] = df['price'].expanding().max()
df['cumulative_sum'] = df['volume'].expanding().sum()Shift (lag values)
df['price_lag1'] = df['price'].shift(1) # Previous value
df['price_change'] = df['price'] - df['price'].shift(1)Pct_change (percentage change)
df['price_pct_change'] = df['price'].pct_change() * 100Rank
df['price_rank'] = df['price'].rank()print(df.head())
Vectorized String Operations
Pandas provides .str accessor for efficient string operations on Series:
import pandas as pddf = pd.DataFrame({
'name': ['Alice Johnson', 'Bob Smith', 'Charlie Brown'],
'email': ['alice@example.com', 'bob@example.com', 'charlie@example.com']
})
String methods
df['first_name'] = df['name'].str.split().str[0]
df['last_name'] = df['name'].str.split().str[1]
df['name_upper'] = df['name'].str.upper()
df['name_lower'] = df['name'].str.lower()
df['name_length'] = df['name'].str.len()Pattern matching
df['has_a'] = df['name'].str.contains('a', case=False)
df['domain'] = df['email'].str.extract(r'@(.+)')Replace
df['email_masked'] = df['email'].str.replace(r'(\w).@', r'\1@', regex=True)print(df)
Performance: NumPy vs Pandas Vectorization
The speed difference between scalar operations and vectorized operations is dramatic:
import pandas as pd
import numpy as np
import timeCreate large dataset
n = 1000000
df = pd.DataFrame({
'a': np.random.randn(n),
'b': np.random.randn(n)
})Slow: Python loop
start = time.time()
result_slow = []
for i in range(len(df)):
result_slow.append(df.iloc[i]['a'] * 2 + df.iloc[i]['b'])
slow_time = time.time() - start
print(f"Python loop: {slow_time:.4f} seconds")Fast: Vectorized
start = time.time()
result_fast = df['a'] * 2 + df['b']
fast_time = time.time() - start
print(f"Vectorized: {fast_time:.4f} seconds")
print(f"Speedup: {slow_time / fast_time:.1f}x")Vectorization also works with apply() using NumPy-aware functions
df['result'] = np.sqrt(df['a']2 + df['b']2)
Memory Optimization
For large datasets, optimizing dtypes can dramatically reduce memory usage:
import pandas as pddf = pd.DataFrame({
'id': [1, 2, 3, 4, 5],
'category': ['A', 'B', 'A', 'C', 'B'],
'value': [10.5, 20.3, 15.7, 25.1, 30.9]
})
Check memory before optimization
print(df.memory_usage(deep=True))Optimize dtypes
df['id'] = df['id'].astype('int32') # int64 -> int32
df['category'] = df['category'].astype('category') # object -> category
df['value'] = df['value'].astype('float32') # float64 -> float32Check memory after optimization
print(df.memory_usage(deep=True))
print(f"Memory savings: {(1 - df.memory_usage().sum() / df_original.memory_usage().sum()) * 100:.1f}%")For read_csv, specify dtypes upfront
dtypes = {
'id': 'int32',
'category': 'category',
'value': 'float32'
}
df = pd.read_csv('large_file.csv', dtype=dtypes)
Quiz
Question 1: What does df.groupby('column').sum() do?
- A) Sums the entire DataFrame
- B) Groups by column and sums numeric columns within each group ✓
- C) Creates a pivot table
- D) Counts rows per group
Question 2: In pd.merge(df1, df2, how='left'), what rows are included?
- A) Only rows present in both DataFrames
- B) All rows from df1, matched rows from df2 ✓
- C) All rows from both DataFrames
- D) Only rows not in both DataFrames
Question 3: What does df['price'].rolling(window=3).mean() compute?
- A) The global mean
- B) Moving average over windows of 3 rows ✓
- C) The standard deviation
- D) Cumulative values
Question 4: Which operation converts categories to data type to save memory?
- A)
df['col'].astype('int32')
- B)
df['col'].astype('category')✓
- C)
df['col'].astype('float32')
- D)
df['col'].astype('object')
Question 5: What does df['name'].str.upper() do?
- A) Returns the number of uppercase letters
- B) Converts all strings to uppercase ✓
- C) Checks if strings are uppercase
- D) Removes uppercase letters
Question 6: Vectorized operations are faster than loops because:
- A) They use Python's native speed
- B) They run in optimized C code without interpreter overhead ✓
- C) They use more memory
- D) They parallelize automatically