Python Pandas Tutorial for Beginners: Series, DataFrames and Real Examples
A marks sheet, a sales report, a list of customers. Most of the data you meet at work sits in rows and columns. Pandas is the Python library that handles such tables the way a spreadsheet would, but with code that you can repeat, test and share. Cleaning a column, joining two tables or finding the average by group takes one line.
Pandas is the most used Python library for data analysis. It builds on NumPy, and libraries such as Matplotlib, Seaborn and scikit learn work with its tables directly. Learn pandas well, and you can read a file, fix it, summarise it and prepare it for a chart or a model.
This tutorial takes you from installation to real work. We cover the two main structures, Series and DataFrame, then summary statistics, loops, text methods, window functions, SQL style queries and joins. We also look at missing data, and at what changed in pandas 3.0, which affects code written in older tutorials. Every example was run on pandas 3.0.5, and the output you see is the real output.
What is Pandas in Python?
Pandas is a free, open source Python library for working with data. It was started by Wes McKinney in 2008, while he was a researcher at AQR Capital Management, and was released as open source in 2009. The name comes from panel data, a term from economics for data that follows the same things over time. It is also a play on Python data analysis.
Pandas helps you do five main jobs with data: load it, clean it, change its shape, explore it and prepare it for modelling. It is used in finance, economics, science, marketing and many other fields.
Features of Pandas
Loading data from CSV, Excel, SQL databases, JSON and more.
Handling missing data with simple methods to find, fill or drop it.
Reshaping data with pivot, merge and concat.
Selecting data by label or by position.
Adding and removing columns in one line.
Grouping data with groupby, to count, add or average by category.
Time series tools such as rolling averages and date ranges.
How to Install Pandas
One command works on Windows, Mac and Linux. Open a terminal, or Command Prompt on Windows, and run:
terminal
pip install pandas
If pip is not found, use python3 or py before it, as in python -m pip install pandas. Then check the version:
python
import pandas as pd
import numpy as np
print(pd.__version__)
output
3.0.5
Your version may be a little different. The pandas 3.0 release notes say pandas 3 supports Python 3.11 and higher, and needs NumPy 1.26 or later. If you use PyCharm, you can also install it from File, then Settings, then your project, then Python Interpreter, and the plus button. The standard short name for pandas is pd, and everybody expects it.
Data Structures in Pandas
Pandas has two main data structures. A Series is one column of labelled values. A DataFrame is a table of rows and columns, and each column is a Series. Older tutorials also list Panel as a third structure, but Panel is gone, and we explain that below.
Mutability
The values inside both a Series and a DataFrame can be changed. A DataFrame can also grow, because you can add and remove columns. For a Series, treat the size as fixed, and build a new one with pd.concat if you need more items.
Series in Pandas
A Series is a one dimensional labelled array. Each value has a label, called the index. If you do not give labels, pandas uses 0, 1, 2 and so on. The syntax is pd.Series(data, index, dtype, copy).
Create a Series From a List
python
marks = pd.Series([72, 88, 91, 65])
print(marks)
output
0 72
1 88
2 91
3 65
dtype: int64
The left column is the index, and the right column is the values. The last line shows the data type, int64.
Pass the array to pd.Series. Use brackets in print(s), because Python 3 needs them.
python
a = np.array([1, 4, 5, 6, 7])
s = pd.Series(a)
print(s)
output
0 1
1 4
2 5
3 6
4 7
dtype: int64
Create a Series From a Dictionary
The keys become the index. The order of the dictionary is kept, and the keys are not sorted.
python
d = {"a": "add", "s": "sub", "d": "dvd"}
s = pd.Series(d)
print(s)
output
a add
s sub
d dvd
dtype: str
Create a Series From a Single Value
A single value is repeated to match the index.
python
s = pd.Series(4, index=[0, 1, 2])
print(s)
output
0 4
1 4
2 4
dtype: int64
Access Data in a Series by Position
python
s = pd.Series([1, 4, 5, 6, 7])
print(s[2])
print(s.iloc[2])
output
5
5
Here the labels and the positions are the same, so both lines give 5. When the labels are words, as in the marks example, use .iloc to be sure that you mean position, and use the label for the name.
DataFrames in Pandas
A DataFrame is a two dimensional table. Each column can hold a different type. The syntax is pd.DataFrame(data, index, columns, dtype, copy). Note that the argument is columns with an s, and that the NumPy function for a range of numbers is np.arange.
one two three
a 2.0 2 100.0
b 3.0 3 200.0
c 4.0 4 300.0
d NaN 5 NaN
one two three four
a 2.0 2 100.0 102.0
b 3.0 3 200.0 203.0
c 4.0 4 300.0 304.0
d NaN 5 NaN NaN
Delete a Column
There are three ways. del is a statement, and pop is a method that also gives back the column. The method drop is worth knowing too, because it gives you a new table and leaves the old one as it is.
python
del dfs["one"]
print(dfs)
popped = dfs.pop("two")
print(popped)
print(dfs)
print(dfs.drop(columns=["four"]))
output
two three four
a 2 100.0 102.0
b 3 200.0 203.0
c 4 300.0 304.0
d 5 NaN NaN
a 2
b 3
c 4
d 5
Name: two, dtype: int64
three four
a 100.0 102.0
b 200.0 203.0
c 300.0 304.0
d NaN NaN
three
a 100.0
b 200.0
c 300.0
d NaN
What Happened to Panel?
Panel was a three dimensional container with items, major axis and minor axis. It was deprecated in pandas 0.20 and removed in 0.25. On current pandas it does not exist, so any code that uses it fails:
python
pd.Panel(np.random.rand(2, 3, 4))
output
AttributeError: module 'pandas' has no attribute 'Panel'
For data with more than two dimensions, use a DataFrame with a MultiIndex, or the xarray library.
Series Basic Functions
These attributes and methods tell you about a Series. They run on a fixed Series, so that you can compare your result.
0 1 2
Name Tom Dick Harry
Age 20 21 19
[RangeIndex(start=0, stop=3, step=1), Index(['Name', 'Age'], dtype='str')]
Name str
Age int64
dtype: object
False
2
(3, 2)
6
[['Tom' 20]
['Dick' 21]
['Harry' 19]]
Name Age
0 Tom 20
1 Dick 21
Name Age
1 Dick 21
2 Harry 19
DataFrame attributes and methods
Name
What it gives
T
The table flipped, so rows become columns
axes
The row labels and the column labels
dtypes
The data type of each column
empty
True if the table has no data
ndim
The number of dimensions, 2 for a table
shape
The number of rows and columns
size
The number of cells
values
The data as a NumPy array
head(n) and tail(n)
The first or last n rows
In pandas 3.0, the Name column shows the type str. Older versions showed object. We explain this at the end.
Descriptive Statistics in Pandas
Here is a table of names, ages and ratings for 12 people. Twelve summary methods follow.
Age 31.250000
Rating 4.099167
dtype: float64
Age Rating
count 12.00 12.00
mean 31.25 4.10
std 9.79 1.26
min 21.00 1.78
25% 23.00 3.60
50% 29.50 4.04
75% 35.50 4.65
max 51.00 6.80
Iterating Over a Pandas Table
Iterating over a Series gives its values. Iterating over a DataFrame gives the column names. Loops over rows are usually slow, so use them only for small tables, or when you cannot do the job with a built in method. The three methods are items, iterrows and itertuples.
items
This goes column by column. The older name iteritems was removed in pandas 2.0, and gives this error now:
python
stats.iteritems()
output
AttributeError: 'DataFrame' object has no attribute 'iteritems'
Use items instead:
python
for col, values in stats.head(3).items():
print(col, list(values))
output
Name ['Tomy', 'Jimy', 'Ricky']
Age [22, 23, 26]
Rating [5.23, 3.44, 3.95]
iterrows
This goes row by row, and gives the index and the row. Mind the spelling, iterrows with an s.
python
for index, row in stats.head(3).iterrows():
print(index, row["Name"], row["Age"])
output
0 Tomy 22
1 Jimy 23
2 Ricky 26
itertuples
This is faster than iterrows, and gives each row as a named tuple.
Pandas has string methods that work on a whole column at once. They sit behind the str accessor, and they skip missing values. Here is a Series with one missing value and one number in quotes:
python
s = pd.Series(["Tom", "Dick", "Harry", "Allen", np.nan, "6234", "SteveJobs"])
print(s)
output
0 Tom
1 Dick
2 Harry
3 Allen
4 NaN
5 6234
6 SteveJobs
dtype: str
Note the last line. In pandas 3.0 the type is str. In older versions, you would see object. Now the common methods:
swapcase needs brackets, as in swapcase(). Without them you get the method, and not the result.
isupper checks if the text is in capitals, and upper changes it. They are not the same.
The method is isnumeric. A method called numeric does not exist.
cat joins the items with the separator that you give. If you give none, it joins them with no space.
The wrong name gives this error:
python
s.str.numeric()
output
AttributeError: 'StringMethods' object has no attribute 'numeric'
The method get_dummies turns a text column that has several tags in one cell into a table of ones and zeros. It needs a separator in the text, and does not work on free text. Here is an example with a vertical bar as the separator:
Window functions smooth data, and make trends easier to see. They are common in time series, such as daily sales or prices. The number of rows must match the number of dates. If it does not, pandas stops with an error:
ValueError: Shape of passed values is (10, 4), indices imply (5, 4)
Random numbers also change from one run to another unless you set a seed. Here the data is fixed, with 6 rows and 6 dates, so that you can match every number.
W X
2026-02-02 10 5
2026-02-03 12 6
2026-02-04 11 8
2026-02-05 15 7
2026-02-06 14 9
2026-02-07 18 10
rolling
Rolling takes a moving window of a set size. With window=4 each result is the mean of the current and the three rows before it, which is four values. The first three rows are NaN, because there are not yet four values.
python
print(w.rolling(window=4).mean())
output
W X
2026-02-02 NaN NaN
2026-02-03 NaN NaN
2026-02-04 NaN NaN
2026-02-05 12.0 6.5
2026-02-06 13.0 7.5
2026-02-07 14.5 8.5
Check the first number by hand. The mean of 10, 12, 11 and 15 is 12.0, which matches.
expanding
Expanding takes all rows from the start up to the current row. With min_periods=3 it starts giving results at the third row.
python
print(w.expanding(min_periods=3).mean().round(3))
output
W X
2026-02-02 NaN NaN
2026-02-03 NaN NaN
2026-02-04 11.000 6.333
2026-02-05 12.000 6.500
2026-02-06 12.400 7.000
2026-02-07 13.333 7.500
Check the third row. The mean of 10, 12 and 11 is 11.0, which matches.
ewm
Ewm is the exponentially weighted mean. Recent rows get more weight than old ones. The setting com=0.5 sets how fast the weight falls off.
python
print(w.ewm(com=0.5).mean().round(3))
output
W X
2026-02-02 10.000 5.000
2026-02-03 11.500 5.750
2026-02-04 11.154 7.308
2026-02-05 13.750 7.100
2026-02-06 13.917 8.372
2026-02-07 16.643 9.459
Using SQL Style Queries in Pandas
If you know SQL, many pandas jobs will look familiar. To keep the code simple and make it run anywhere, here is a small table with the same columns as the well known tips dataset.
total_bill tip sex smoker time
0 16.99 1.01 Female No Dinner
1 10.34 1.66 Male No Dinner
2 21.01 3.50 Male No Dinner
3 23.68 3.31 Male No Lunch
4 24.59 3.61 Female No Dinner
total_bill tip smoker time
0 16.99 1.01 No Dinner
1 10.34 1.66 No Dinner
2 21.01 3.50 No Dinner
3 23.68 3.31 No Lunch
4 24.59 3.61 No Dinner
WHERE
python
print(tips[tips["time"] == "Dinner"].head(5))
output
total_bill tip sex smoker time
0 16.99 1.01 Female No Dinner
1 10.34 1.66 Male No Dinner
2 21.01 3.50 Male No Dinner
4 24.59 3.61 Female No Dinner
6 8.77 2.00 Male No Dinner
GROUP BY
The size method counts the rows in each group, and the second line takes the mean of one column.
sex
Female 2
Male 6
dtype: int64
sex
Female 2.31
Male 3.05
Name: tip, dtype: float64
TOP N
python
print(tips.nlargest(3, "tip"))
output
total_bill tip sex smoker time
5 25.29 4.71 Male No Lunch
4 24.59 3.61 Female No Dinner
2 21.01 3.50 Male No Dinner
Reading a CSV File
In real work, the table comes from a file. The method is read_csv. Here the file text is held in memory, so that you can run it without a file. With a real file, pass its name, as in pd.read_csv("marks.csv").
python
from io import StringIO
csv_text = "name,marks\nAsha,72\nRavi,88\nMeena,91\n"
print(pd.read_csv(StringIO(csv_text)))
output
name marks
0 Asha 72
1 Ravi 88
2 Meena 91
SQL Joins in Pandas With merge
The function merge joins two tables on a shared column, just like a SQL join. Here are two tables with five rows each, joined on subject_id.
id_x Name_x subject_id id_y Name_y
0 1.0 Ali sub1 NaN NaN
1 2.0 Any sub2 1.0 Bil
2 NaN NaN sub3 3.0 Bany
3 3.0 Amen sub4 2.0 Briany
4 5.0 Amy sub5 5.0 Betten
5 4.0 Arik sub6 4.0 Brycy
Choosing Clear Suffixes
Both tables have the columns id and Name. Pandas keeps both, and adds _x and _y, as you can see in the results above. You can choose clearer names with the suffixes argument:
name 0
marks 1
dtype: int64
name marks
0 Asha 72.0
1 Ravi 0.0
2 Meena 91.0
name marks
0 Asha 72.0
2 Meena 91.0
Fill with zero only when zero makes sense. For marks, a missing value is not a mark of zero, so dropping the row or filling with the mean may fit better. Decide by the meaning of the data.
What Changed in Pandas 3.0
Pandas 3.0 came out in January 2026, and the latest release at the time of writing is 3.0.6, from 17 September 2026. A lot of older tutorials were written before it. These are the changes that matter to beginners, as listed in the official release notes.
A Dedicated String Type
A column of text now has the type str, and not object. The release notes say it can hold only strings or missing values.
python
print(pd.Series(["a", "b"]).dtype)
output
str
Copy on Write
The result of an indexing operation now always behaves as if it were a copy. Changing one never changes another. The release notes say chained assignment stops working, and the SettingWithCopyWarning is removed.
python
x = pd.DataFrame({"a": [1, 2, 3]})
y = x["a"]
y.iloc[0] = 99
print(x["a"].tolist())
print(y.tolist())
output
[1, 2, 3]
[99, 2, 3]
The table x stayed the same when the new Series y changed. In older pandas, this could change both, which caused many silent bugs. If you wrote code such as df["a"][mask] = value, replace it with the loc form:
Python 3.11 or higher is needed, with NumPy 1.26 or higher.
Strings now convert to dates at a finer unit than before, so the results of to_datetime can have a different type. Be careful when you turn dates into integers, because the numbers can be 1000 times smaller.
The release notes advise upgrading to pandas 2.3 first, fixing the warnings, and only then moving to 3.0.
This list is not complete. For a project at work, read the full release notes before you upgrade.
Common Pandas Errors and How to Fix Them
Pandas errors and fixes
Error
Usual cause
Fix
SyntaxError on print df
Python 2 style print
Write print(df)
SyntaxError on a curly quote
Code copied from a web page or a Word file
Type straight quotes in your editor
NameError: name pd is not defined
The import uses another name
Use import pandas as pd
ValueError: If using all scalar values, you must pass an index
A dictionary of single values
Use lists as values, or pass an index
ValueError: Shape of passed values
More rows of data than index labels
Make the index the same length as the data
TypeError: Cannot perform reduction mean with string dtype
A number method on a text column
Select the number columns, or use numeric_only=True
AttributeError: no attribute iteritems
A method removed in pandas 2.0
Use items
AttributeError: module pandas has no attribute Panel
Panel was removed
Use a DataFrame with a MultiIndex
AttributeError: dict has no attribute T
A dictionary used as a table
Wrap it with pd.DataFrame
Practice Exercises
Build a DataFrame of five students with a name, a course and marks. Print the average mark per course with groupby.
Add a column called grade. Give Pass if marks are 70 or more, and Needs work if not. Use np.where.
Add one missing mark and fill it with the mean of the column.
Build a second table of courses and their teachers, and merge it with the first on the course name. Try an inner and a left join.
Make a column of daily sales for 10 days, and compute a 3 day rolling mean.
Complete Script to Run Yourself
Here is the full script that was used to test every example on this page. It ran on pandas 3.0.5 and NumPy 2.5.3. The blocks that show an error are written to fail on purpose, so run those one at a time, or wrap them in try and except.