DataFrame
When working with real-world datasets, you often encounter tabular data: rows representing observations and columns representing features. Pandas provides the DataFrame as its core structure for handling this kind of data. Think of a DataFrame as a spreadsheet or a SQL table, but with powerful programmatic capabilities for manipulation, analysis, and cleaning. It is the workhorse of data science in Python, enabling efficient operations on structured data.
DataFrame Structure: Columns of Series
A DataFrame is fundamentally a collection of Pandas Series objects, where each Series represents a column. All Series within a DataFrame share the same index, which provides row labels and ensures alignment across columns. This shared index is what makes DataFrames so powerful for aligning heterogeneous data types, allowing you to combine numerical, categorical, and textual data seamlessly within a single structure. Each column can hold a different data type, making DataFrames highly flexible.
Creating a DataFrame from a Dictionary
One of the most common ways to construct a DataFrame is from a dictionary where keys become column names and values are lists or NumPy arrays representing the column data. Pandas automatically infers the data types and creates a default integer index if one is not explicitly provided. This method is intuitive for building DataFrames from structured data that already exists in Python's native dictionary format, often from JSON or API responses.
Accessing Columns
You can access individual columns of a DataFrame using dictionary-like notation (square brackets) or attribute-like notation (dot notation). When you select a single column, Pandas returns a Series object, reinforcing the idea that DataFrames are collections of Series. Using square brackets is generally preferred, especially when column names contain spaces or conflict with DataFrame method names, as it is more robust.
Accessing Rows with .loc and .iloc
Pandas provides two primary methods for selecting rows: .loc for label-based indexing and .iloc for integer-position-based indexing. Understanding the distinction is critical to avoid common errors. .loc uses the actual labels of the index and columns, while .iloc uses the zero-based integer positions, similar to Python list indexing. This dual approach offers flexibility, allowing you to select data based on either semantic labels or absolute positions.
When slicing with .loc, both the start and end labels are inclusive. For example, df.loc['A':'C'] includes rows 'A', 'B', and 'C'. In contrast, .iloc slicing is exclusive of the end position, just like standard Python list slicing. For example, df.iloc[0:2] includes rows at positions 0 and 1, but not 2.
Adding and Modifying Columns
Adding new columns or modifying existing ones in a DataFrame is straightforward and often involves assigning a Series or a scalar value to a new or existing column name. When you assign a Series, its index is automatically aligned with the DataFrame's index, ensuring that values are placed correctly. If the Series index does not match, Pandas will insert NaN (Not a Number) for non-matching index labels, which is a common way to handle missing data. This automatic alignment is a powerful feature that simplifies data integration.
AnnualBonus to the staff_df where employees in 'Engineering' get 10% of their salary as bonus, and others get 5% of their salary. Then, display the updated DataFrame.DataFrame Attributes
DataFrames come with several useful attributes that provide quick insights into their structure and contents without needing to call methods. These attributes are essential for understanding the dimensions, data types, and labels of your data. For instance, .shape tells you the number of rows and columns, while .dtypes reveals the data type of each column, which is critical for debugging and ensuring data integrity. Regularly checking these attributes helps maintain awareness of your DataFrame's state.
df.values be useful, even though it discards column and index labels?Immutability of Operations
Many DataFrame operations, especially those that involve filtering, sorting, or creating new columns, do not modify the original DataFrame in place. Instead, they return a new DataFrame with the changes applied. This behavior is crucial for maintaining data integrity and preventing unintended side effects, especially in complex analysis pipelines. If you want to apply changes directly to the original DataFrame, you often need to explicitly assign the result back to the original variable or use the inplace=True argument where available (though explicit assignment is generally safer and preferred).
| Feature | Pandas Series | Pandas DataFrame |
|---|---|---|
| Dimensionality | 1-dimensional | 2-dimensional |
| Structure | Single column of data with an index | Collection of Series (columns) sharing a common index |
| Homogeneity | Homogeneous data types (all elements of one type) | Heterogeneous data types (each column can have a different type) |
| Analogy | A single column in a spreadsheet | An entire spreadsheet or SQL table |
| Primary Use | Representing a single feature/variable | Representing entire datasets with multiple features |
A DataFrame is Pandas' primary 2D tabular data structure, analogous to a spreadsheet or SQL table, designed for structured data.
DataFrames are composed of multiple Series objects, where each Series represents a column and all Series share a common index (row labels).
You can create DataFrames from dictionaries (keys as columns, values as data lists), lists of dictionaries, or NumPy arrays.
Access columns using
df['ColumnName'](returns a Series) ordf[['Col1', 'Col2']](returns a DataFrame).Access rows using
.locfor label-based indexing (inclusive slicing) and.ilocfor integer-position-based indexing (exclusive slicing).Adding or modifying columns is done via assignment; Pandas automatically aligns data by index, inserting
NaNfor mismatches.Many DataFrame operations return a new DataFrame, leaving the original unchanged, which is critical for predictable data pipelines.