Knowledge base / Python

pandas.Series: creation, indexes and selecting values

I explain labels versus positions, alignment between two Series and the effect of missing values on a sum. Includes a correction to the old s[0] example with a text index.

Kajo RudzińskiPublished Updated 6 min read

Three containers on one rail with separate geometric labels and a selector

When I examine a column, I care about its values and what they belong to. Ten might be a product’s stock, a measurement or a count of orders. A pandas.Series holds one-dimensional data with an index that lets me refer to particular elements. I check both parts before calculating. The appearance of the numbers alone cannot tell me whether I am comparing the right observations.

The examples here use small synthetic datasets. They describe pandas 3.x and are derived from documentation, without claiming they were executed. I focus on creation, selection and simple operations. The Series reference provides the full method catalogue. The main correction to the earlier article concerns distinguishing a label from a position when retrieving a value.

Create a Series from a list or dictionary

I begin with three values and give them product labels. I explicitly choose Int64, the pandas nullable integer type. The capital letter in the type name matters. Choosing it makes the explanation independent of automatic type inference after a change to the input data.

import pandas as pd

stock = pd.Series(
    [10, 20, 30],
    index=['x', 'y', 'z'],
    dtype='Int64',
    name='units',
)
Label Value
x 10
y 20
z 30

The value list and supplied index have equal lengths. Without an explicit index, pandas creates a default RangeIndex starting at zero. Those labels can look like positions, encouraging an unhelpful shortcut in reasoning. Product labels make the distinction visible. The name parameter describes the Series without changing which label belongs to which value.

A dictionary is another possible input:

from_dict = pd.Series({'x': 10, 'y': 20, 'z': 30}, dtype='Int64')

Dictionary keys become index labels and dictionary values become data. Supplying an additional index selects data according to those labels. Before working with a new example, I establish whether its index identifies products, dates or simply an order. That choice affects later selection and alignment.

loc selects labels; iloc selects positions

The loc reference describes label-based selection. The iloc reference covers integer positions. Here, label y occupies the second position, numbered one because positional numbering starts at zero.

stock.loc['y']     # 20
stock.iloc[0]      # 10
stock.iloc[1]      # 20
stock.loc[['z', 'x']]

The final selection gives labels z, x with values 30 and 10 respectively, following the requested label order. A requested label absent from this kind of selection raises KeyError. A single out-of-range positional selection raises IndexError. Slices have their own rules, so that description should not be extended to every possible selection syntax.

Why the old s[0] example needs changing

For this stock, stock[0] looks for label zero. That label is absent, so pandas 3.x raises KeyError. The pandas 3.0 changes made integer keys consistently label-based. Retrieving the first element of a text-indexed Series through that expression is no longer a valid current pattern.

I make the intention explicit: loc for a label and iloc for a position. The distinction still matters with numeric labels. Label ten does not mean the tenth position. Reordering data can change which product a positional selection retrieves while the product’s label stays the same. I choose the method according to the task’s meaning rather than the shortest expression.

The index can contain duplicates

A Series index does not have to be unique. This is allowed:

repeated = pd.Series([10, 20], index=['x', 'x'], dtype='Int64')
repeated.loc['x']

Selection returns both values carrying label x. That differs from expecting every label to retrieve one scalar. The old claim that indexes were necessarily unique was incorrect. If your index is meant to identify one product, a repeated label needs investigating. It might indicate an error or several valid observations of the same product.

I would not remove duplicates just to make selection convenient. First I establish what each observation represents. Two values may concern different warehouses or dates. A further key or an explicit aggregation may then be appropriate. Assigning an index does not itself guarantee that the data model is correct.

Containers on two rails connect through matching geometric labels

Filtering preserves labels

A comparison produces a mask that can select values from the same Series:

selected = stock.loc[stock > 15]

The result contains y with 20 and z with 30. Their labels do not automatically become zero and one. To retrieve the first remaining value, I use selected.iloc[0]. Selecting selected.loc['y'] still refers to that particular product regardless of its position in the sequence.

A mask from another object requires additional attention to index alignment. Here, the condition comes directly from stock, keeping somebody else’s ordering out of the example. When a filter result looks surprising, I inspect labels and missing values alongside the number of returned elements.

Addition aligns on index labels

The second Series uses its labels in the reverse order:

extra = pd.Series([7, 5], index=['z', 'y'], dtype='Int64')
total = stock + extra
Label stock extra total
x 10 missing missing
y 20 5 25
z 30 7 37

Pandas matches y with y and z with z. It does not simply add the first value on one side to the first on the other. Label x has no right-hand operand, so ordinary addition produces a missing result. With these nullable integer inputs, the missing marker is pd.NA. The table describes the values without depending on console formatting.

The Series.add method accepts fill_value=0. Here, stock.add(extra, fill_value=0) gives 10 for x, 25 for y and 37 for z. I use it only after establishing that an absent additional amount means zero. No information about a delivery and a confirmed absence of delivery can require different interpretations.

Missing values and a sum

Replace the middle observation with a missing value:

incomplete = pd.Series([10, pd.NA, 30], dtype='Int64')
incomplete.isna()
incomplete.sum()
incomplete.sum(min_count=3)

The isna() mask identifies the middle position as missing. The default sum skips missing values and gives 40. The final call requires three non-missing observations, but only two exist, so it returns a missing value. The sum reference explains skipna and min_count.

In a report, I need to know whether 40 is the complete total or the sum of the known part. Correct arithmetic cannot resolve that interpretation. I establish the required completeness and show it with the result or enforce a suitable threshold. Missing values should not become zero without deciding what their absence means.

Exercise and next step

Change the order of extra while keeping each value attached to its label. Predict whether addition for y changes before reaching for a tool. Then consider a label absent from stock: where would alignment create a missing result? This exercise separates ordering from observation identity without needing a large dataset.

When moving from one Series to a table, continue with DataFrame. For combining data through keys, see pandas.merge. The recurring question is what identifies an observation and whether the result preserves its meaning.

Key takeaways

Does s[0] always return the first Series element?

No. From pandas 3.0, an integer key is a label. Use s.iloc[0] for the first position and s.loc[0] for label 0, when that label exists.

Must a Series index be unique?

No. Duplicate labels are allowed. Selecting such a label with loc can return several values, so do not automatically expect a scalar result.

Why does adding two Series produce missing values?

Values align on index labels. When a label exists on only one side, ordinary addition produces a missing result for that label.

AI was used in an automated way while working on this article. If you want to learn how to do it too, check out this project.

Kajo Rudziński

Newsletter KajoData

Let’s keep in touch.

I write about data analysis, learning and changing careers. My newsletter and the sign-up form are in Polish. If that works for you, I’d be glad to see you there.