Python Pandas - DataFrame
Pandas - DataFrame
Section titled “Pandas - DataFrame”A DataFrame is the workhorse data structure in Pandas. It’s a two-dimensional, size-mutable, and potentially heterogeneous tabular data structure with labeled axes (rows and columns). Think of it like a spreadsheet, an SQL table, or a dictionary of Series objects.
Key Features of DataFrame
Section titled “Key Features of DataFrame”- Columns can hold different data types (integers, strings, floats, booleans, etc.).
- Both rows and columns have labels, referred to as the Index and Columns respectively.
- Size is mutable: you can add or delete columns and rows.
- Supports arithmetic operations on both rows and columns.
- Efficiently handles missing data (represented as
NaN). - Provides powerful tools for slicing, dicing, grouping, merging, and reshaping data.
Conceptual Structure
Section titled “Conceptual Structure”Imagine a table containing student data:
Rows might be identified by a student ID (the index), and columns might represent ‘Name’, ‘Age’, ‘Major’, ‘GPA’ (the columns). Each column holds data of potentially different types.
Name Age Major GPAIndex101 Alice 20 Economics 3.8102 Bob 21 Physics 3.5103 Charlie 19 History 3.9...Creating a DataFrame: pd.DataFrame()
Section titled “Creating a DataFrame: pd.DataFrame()”The primary way to create a DataFrame is using the pandas.DataFrame constructor:
pandas.DataFrame(data=None, index=None, columns=None, dtype=None, copy=None)Constructor Parameters:
| Parameter | Description |
|---|---|
| data | The data to populate the DataFrame. Can be various types: NumPy ndarray, dictionary, list, Series, another DataFrame, etc. |
| index | Labels for the rows. Optional; defaults to a RangeIndex (0, 1, 2, …) if not provided. |
| columns | Labels for the columns. Optional; inferred from the input data (e.g., dict keys) or defaults to a RangeIndex if not provided and not inferrable. |
| dtype | Forces a specific data type for all columns. If None, types are inferred per column. |
| copy | Deprecated in recent Pandas versions. Previously controlled whether to copy input data (default False). Data copying behavior is now more nuanced; see Pandas documentation for specifics. |
Ways to Create DataFrames
Section titled “Ways to Create DataFrames”DataFrames can be created from various common data structures:
- Dictionary of lists or NumPy arrays
- List of dictionaries
- Dictionary of Series
- List of lists or NumPy array
- Another DataFrame
Create an Empty DataFrame
Section titled “Create an Empty DataFrame”You can create an empty DataFrame, which might be populated later.
Example: Empty DataFrame
Section titled “Example: Empty DataFrame”# Import pandas, aliased as pd (standard convention)import pandas as pd
empty_df = pd.DataFrame()print(empty_df)Output:
Empty DataFrameColumns: []Index: []Create from Lists
Section titled “Create from Lists”You can create a DataFrame from a simple list (resulting in a single column) or a list of lists (where each inner list becomes a row).
Example 1: From a single list
Section titled “Example 1: From a single list”import pandas as pd
data_list_simple = [10, 20, 30, 40, 50]df_from_list = pd.DataFrame(data_list_simple, columns=['Numbers'])print(df_from_list)Output:
Numbers0 101 202 303 404 50Example 2: From a list of lists
Section titled “Example 2: From a list of lists”import pandas as pd
data_list_of_lists = [ ['Alex', 25, 'Software Engineer'], ['Bob', 30, 'Data Scientist'], ['Clarke', 22, 'Web Developer']]
# Provide column namesdf_from_lol = pd.DataFrame(data_list_of_lists, columns=['Name', 'Age', 'Job Title'])print(df_from_lol)Output:
Name Age Job Title0 Alex 25 Software Engineer1 Bob 30 Data Scientist2 Clarke 22 Web DeveloperExample 3: Specifying Data Type
Section titled “Example 3: Specifying Data Type”import pandas as pdimport numpy as np # For np.float64
data_list_of_lists = [ ['Alex', 10], ['Bob', 12], ['Clarke', 13]]
# Specify dtype for 'Age' column during creation (more common post-creation)# Or specify a single dtype for all applicable columnsdf_typed = pd.DataFrame(data_list_of_lists, columns=['Name', 'Age'], dtype=np.float64) # Applies float64 where possible
print(df_typed)print('\nData Types:')print(df_typed.dtypes)Output:
Name Age0 Alex 10.01 Bob 12.02 Clarke 13.0
Data Types:Name object # Name cannot be float, stays as objectAge float64dtype: objectNote: The dtype parameter tries to cast columns. If a column cannot be cast (like ‘Name’ to float), its original inferred type (‘object’ for strings) is kept. It’s often more practical to adjust dtypes after creation using .astype().
Create from Dict of Lists or Arrays
Section titled “Create from Dict of Lists or Arrays”This is a very common and often efficient way to create DataFrames. Dictionary keys become column names, and the lists or arrays become the column data. All lists/arrays must have the same length.
If no index is provided, a default RangeIndex (0, 1, 2, …) is created.
Example 1: Basic creation
Section titled “Example 1: Basic creation”import pandas as pd
data_dict = { 'Student Name': ['Tom', 'Jack', 'Steve', 'Ricky'], 'Score': [85, 92, 78, 88]}
df_from_dict = pd.DataFrame(data_dict)print(df_from_dict)Output:
Student Name Score0 Tom 851 Jack 922 Steve 783 Ricky 88Note: The default index 0, 1, 2, 3 is automatically assigned.
Example 2: Providing a custom index
Section titled “Example 2: Providing a custom index”import pandas as pd
data_dict = { 'Student Name': ['Tom', 'Jack', 'Steve', 'Ricky'], 'Score': [85, 92, 78, 88]}custom_index = ['ID_101', 'ID_102', 'ID_103', 'ID_104']
df_indexed = pd.DataFrame(data_dict, index=custom_index)print(df_indexed)Output:
Student Name ScoreID_101 Tom 85ID_102 Jack 92ID_103 Steve 78ID_104 Ricky 88Note: The index parameter assigns meaningful labels to the rows.
Create from List of Dicts
Section titled “Create from List of Dicts”Each dictionary in the list represents a row. Dictionary keys are automatically inferred as column names. If a dictionary is missing a key that exists in other dictionaries, NaN (Not a Number) will be placed in that cell.
Example 1: Missing data
Section titled “Example 1: Missing data”import pandas as pd
data_list_of_dicts = [ {'col_a': 1, 'col_b': 2}, {'col_a': 5, 'col_b': 10, 'col_c': 20}, # This dict has 'col_c' {'col_a': 9, 'col_b': 15} # This one doesn't]
df_from_lod = pd.DataFrame(data_list_of_dicts)print(df_from_lod)Output:
col_a col_b col_c0 1 2 NaN1 5 10 20.02 9 15 NaNNote: NaN is automatically inserted where data is missing (like ‘col_c’ in the first and third rows). Pandas infers the most suitable dtype for ‘col_c’ (float64 here, because NaN is a float).
Example 2: Specifying Index
Section titled “Example 2: Specifying Index”import pandas as pd
data_list_of_dicts = [ {'col_a': 1, 'col_b': 2}, {'col_a': 5, 'col_b': 10, 'col_c': 20}]row_labels = ['Row_1', 'Row_2']
df_lod_indexed = pd.DataFrame(data_list_of_dicts, index=row_labels)print(df_lod_indexed)Output:
col_a col_b col_cRow_1 1 2 NaNRow_2 5 10 20.0Example 3: Specifying Columns
Section titled “Example 3: Specifying Columns”You can explicitly specify the columns and their order. If a specified column doesn’t exist as a key in the dictionaries, it will be filled with NaN.
import pandas as pd
data_list_of_dicts = [ {'col_a': 1, 'col_b': 2}, {'col_a': 5, 'col_b': 10, 'col_c': 20}]row_labels = ['Row_1', 'Row_2']
# Select and reorder existing columnsdf1 = pd.DataFrame(data_list_of_dicts, index=row_labels, columns=['col_b', 'col_a'])
# Select existing columns and one non-existing column ('col_d')df2 = pd.DataFrame(data_list_of_dicts, index=row_labels, columns=['col_a', 'col_c', 'col_d'])
print("DataFrame df1 (Selected existing columns):")print(df1)print("\nDataFrame df2 (Includes non-existing column 'col_d'):")print(df2)Output:
DataFrame df1 (Selected existing columns): col_b col_aRow_1 2 1Row_2 10 5
DataFrame df2 (Includes non-existing column 'col_d'): col_a col_c col_dRow_1 1 NaN NaNRow_2 5 20.0 NaNNote: df1 shows columns ‘col_b’ and ‘col_a’ in the specified order. df2 includes ‘col_a’ and ‘col_c’ (with NaN where missing) and adds a new column ‘col_d’ filled entirely with NaN because it wasn’t in the input dictionaries.
Create from Dict of Series
Section titled “Create from Dict of Series”Dictionary keys become column names, and the Series values become the column data. The resulting DataFrame’s index will be the union of all indices from the input Series. Missing values will be filled with NaN where indices don’t align.
Example
Section titled “Example”import pandas as pd
data_dict_of_series = { 'ColOne': pd.Series([1.0, 2.0, 3.0], index=['a', 'b', 'c']), 'ColTwo': pd.Series([1.0, 2.0, 3.0, 4.0], index=['a', 'b', 'c', 'd'])}
df_from_dos = pd.DataFrame(data_dict_of_series)print(df_from_dos)Output:
ColOne ColTwoa 1.0 1.0b 2.0 2.0c 3.0 3.0d NaN 4.0Note: The final index is (‘a’, ‘b’, ‘c’, ‘d’). Since ‘ColOne’ didn’t have an index ‘d’, NaN is placed in that cell.
Column Operations
Section titled “Column Operations”Selecting, adding, and deleting columns are fundamental DataFrame operations.
Column Selection
Section titled “Column Selection”Access columns using dictionary-like square bracket notation [] or attribute access . (if the column name is a valid Python identifier and doesn’t conflict with DataFrame methods).
# Using df_from_dos from the previous exampleprint("Selecting 'ColOne' using []:")print(df_from_dos['ColOne'])
# print("\nSelecting 'ColTwo' using attribute access:") # Use with caution# print(df_from_dos.ColTwo)
print("\nSelecting multiple columns [['ColTwo', 'ColOne']]:")print(df_from_dos[['ColTwo', 'ColOne']]) # Pass a list of column namesOutput:
Selecting 'ColOne' using []:a 1.0b 2.0c 3.0d NaNName: ColOne, dtype: float64
Selecting multiple columns [['ColTwo', 'ColOne']]: ColTwo ColOnea 1.0 1.0b 2.0 2.0c 3.0 3.0d 4.0 NaNColumn Addition
Section titled “Column Addition”Add a new column by assigning data to a new column label.
# Using df_from_dosprint("Original DataFrame:")print(df_from_dos)
# Add a new column by assigning a Series (index aligns)print("\nAdding 'ColThree' by assigning a Series:")df_from_dos['ColThree'] = pd.Series([10, 20, 30], index=['a', 'b', 'c'])print(df_from_dos)
# Add a new column by calculation using existing columnsprint("\nAdding 'ColFour' = ColOne + ColThree:")df_from_dos['ColFour'] = df_from_dos['ColOne'] + df_from_dos['ColThree']print(df_from_dos)
# Add a new column with a scalar value (broadcasts to all rows)print("\nAdding 'ColFive' with a scalar value:")df_from_dos['ColFive'] = 100print(df_from_dos)Output:
Original DataFrame: ColOne ColTwoa 1.0 1.0b 2.0 2.0c 3.0 3.0d NaN 4.0
Adding 'ColThree' by assigning a Series: ColOne ColTwo ColThreea 1.0 1.0 10.0b 2.0 2.0 20.0c 3.0 3.0 30.0d NaN 4.0 NaN
Adding 'ColFour' = ColOne + ColThree: ColOne ColTwo ColThree ColFoura 1.0 1.0 10.0 11.0b 2.0 2.0 20.0 22.0c 3.0 3.0 30.0 33.0d NaN 4.0 NaN NaN
Adding 'ColFive' with a scalar value: ColOne ColTwo ColThree ColFour ColFivea 1.0 1.0 10.0 11.0 100b 2.0 2.0 20.0 22.0 100c 3.0 3.0 30.0 33.0 100d NaN 4.0 NaN NaN 100Column Deletion
Section titled “Column Deletion”Use the del keyword or the .pop() or .drop() methods.
# Using df_from_dos from the previous stepprint("DataFrame before deletion:")print(df_from_dos)
# Using del (in-place)print("\nDeleting 'ColOne' using del:")del df_from_dos['ColOne']print(df_from_dos)
# Using pop (removes and returns the column)print("\nDeleting 'ColTwo' using pop:")popped_col = df_from_dos.pop('ColTwo')print("DataFrame after pop:")print(df_from_dos)print("\nPopped Column 'ColTwo':")print(popped_col)
# Using drop (returns a new DataFrame, use inplace=True to modify original)print("\nDeleting 'ColThree' using drop (returns new DataFrame):")df_after_drop = df_from_dos.drop(columns=['ColThree']) # Use 'columns' argument# Or: df_after_drop = df_from_dos.drop(['ColThree'], axis=1)print(df_after_drop)print("\nOriginal DataFrame after non-inplace drop (unchanged):")print(df_from_dos)Output:
DataFrame before deletion: ColOne ColTwo ColThree ColFour ColFivea 1.0 1.0 10.0 11.0 100b 2.0 2.0 20.0 22.0 100c 3.0 3.0 30.0 33.0 100d NaN 4.0 NaN NaN 100
Deleting 'ColOne' using del: ColTwo ColThree ColFour ColFivea 1.0 10.0 11.0 100b 2.0 20.0 22.0 100c 3.0 30.0 33.0 100d 4.0 NaN NaN 100
Deleting 'ColTwo' using pop:DataFrame after pop: ColThree ColFour ColFivea 10.0 11.0 100b 20.0 22.0 100c 30.0 33.0 100d NaN NaN 100
Popped Column 'ColTwo':a 1.0b 2.0c 3.0d 4.0Name: ColTwo, dtype: float64
Deleting 'ColThree' using drop (returns new DataFrame): ColFour ColFivea 11.0 100b 22.0 100c 33.0 100d NaN 100
Original DataFrame after non-inplace drop (unchanged): ColThree ColFour ColFivea 10.0 11.0 100b 20.0 22.0 100c 30.0 33.0 100d NaN NaN 100Row Operations
Section titled “Row Operations”Selecting, adding, and deleting rows involve using the index.
Row Selection: .loc and .iloc
Section titled “Row Selection: .loc and .iloc”Use .loc[] for label-based selection and .iloc[] for integer position-based selection.
import pandas as pd
data_dict = { 'ColOne': pd.Series([1.0, 2.0, 3.0], index=['a', 'b', 'c']), 'ColTwo': pd.Series([1.0, 2.0, 3.0, 4.0], index=['a', 'b', 'c', 'd'])}df = pd.DataFrame(data_dict)print("DataFrame:")print(df)
# Selection by label using .locprint("\nSelecting row with label 'b' using .loc['b']:")print(df.loc['b'])
# Selection by integer position using .ilocprint("\nSelecting row at position 2 (label 'c') using .iloc[2]:")print(df.iloc[2])
# Selecting multiple rows by labelprint("\nSelecting rows 'a' and 'd' using .loc[['a', 'd']]:")print(df.loc[['a', 'd']])
# Selecting multiple rows by positionprint("\nSelecting rows at positions 0 and 3 using .iloc[[0, 3]]:")print(df.iloc[[0, 3]])Output:
DataFrame: ColOne ColTwoa 1.0 1.0b 2.0 2.0c 3.0 3.0d NaN 4.0
Selecting row with label 'b' using .loc['b']:ColOne 2.0ColTwo 2.0Name: b, dtype: float64
Selecting row at position 2 (label 'c') using .iloc[2]:ColOne 3.0ColTwo 3.0Name: c, dtype: float64
Selecting rows 'a' and 'd' using .loc[['a', 'd']]: ColOne ColTwoa 1.0 1.0d NaN 4.0
Selecting rows at positions 0 and 3 using .iloc[[0, 3]]: ColOne ColTwoa 1.0 1.0d NaN 4.0Selecting a single row returns a Series, while selecting multiple rows returns a DataFrame.
Row Slicing
Section titled “Row Slicing”Use slicing with .loc (label-based, endpoint inclusive) or .iloc (integer-based, endpoint exclusive).
# Using df from the previous example
# Slice rows by label (inclusive)print("\nSlicing rows 'b' through 'd' using .loc['b':'d']:")print(df.loc['b':'d'])
# Slice rows by position (exclusive)print("\nSlicing rows from position 1 up to (not including) 3 using .iloc[1:3]:")print(df.iloc[1:3])
# Direct integer slicing on DataFrame (df[1:3]) is possible but generally# discouraged; prefer explicit .iloc for clarity and robustness.# print("\nSlicing using df[1:3] (discouraged):")# print(df[1:3])Output:
Slicing rows 'b' through 'd' using .loc['b':'d']: ColOne ColTwob 2.0 2.0c 3.0 3.0d NaN 4.0
Slicing rows from position 1 up to (not including) 3 using .iloc[1:3]: ColOne ColTwob 2.0 2.0c 3.0 3.0Addition of Rows
Section titled “Addition of Rows”The recommended way to add rows is using pd.concat(). The older .append() method is deprecated and removed in Pandas 2.0 and should not be used.
import pandas as pd
df1 = pd.DataFrame([[1, 2], [3, 4]], columns=['A', 'B'], index=[0, 1])df2 = pd.DataFrame([[5, 6], [7, 8]], columns=['A', 'B'], index=[2, 3]) # Use different indexdf3 = pd.DataFrame([[9, 10], [11, 12]], columns=['A', 'C'], index=[4, 5]) # Different column
print("DataFrame df1:")print(df1)print("\nDataFrame df2:")print(df2)print("\nDataFrame df3:")print(df3)
# Concatenate df1 and df2 (same columns)combined_df = pd.concat([df1, df2])print("\nCombined df1 and df2 using pd.concat():")print(combined_df)
# Concatenate df1 and df3 (different columns, align on index)combined_df_cols = pd.concat([df1, df3]) # outer join by defaultprint("\nCombined df1 and df3 using pd.concat() (outer join):")print(combined_df_cols)
# If using older examples that had duplicate indices from append:# df_old_append = pd.DataFrame([[1, 2], [3, 4]], columns=['A', 'B'], index=[0, 1])# df_old_append2 = pd.DataFrame([[5, 6], [7, 8]], columns=['A', 'B'], index=[0, 1])# combined_dup_index = pd.concat([df_old_append, df_old_append2], ignore_index=True)# print("\nCombined with ignore_index=True to reset index:")# print(combined_dup_index)Output:
DataFrame df1: A B0 1 21 3 4
DataFrame df2: A B2 5 63 7 8
DataFrame df3: A C4 9 105 11 12
Combined df1 and df2 using pd.concat(): A B0 1 21 3 42 5 63 7 8
Combined df1 and df3 using pd.concat() (outer join): A B C0 1.0 2.0 NaN1 3.0 4.0 NaN4 9.0 NaN 10.05 11.0 NaN 12.0pd.concat intelligently combines DataFrames. Use ignore_index=True if you want a fresh default integer index on the result.
Deletion of Rows
Section titled “Deletion of Rows”Use the .drop() method, specifying the row labels (index values) to remove and axis=0 (or omit axis as it defaults to 0).
# Using combined_df from the previous exampleprint("DataFrame before row deletion:")print(combined_df)
# Drop rows with labels 0 and 3# By default, drop returns a new DataFrame. Use inplace=True to modify.df_dropped = combined_df.drop([0, 3], axis=0)print("\nDataFrame after dropping rows 0 and 3:")print(df_dropped)
# Example with duplicate labels (if created that way previously)# df_dup = pd.DataFrame([[1,2],[3,4],[5,6]], columns=['A','B'], index=[0,1,0])# print("\nDataFrame with duplicate index 0:")# print(df_dup)# print("\nDropping label 0 (removes all rows with that label):")# print(df_dup.drop(0))Output:
DataFrame before row deletion: A B0 1 21 3 42 5 63 7 8
DataFrame after dropping rows 0 and 3: A B1 3 42 5 6If an index label is duplicated, drop() will remove all rows matching that label.