Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Analysis with Python (pandas, polars, DuckDB)  Module 7 – Reshaping: tidy data, melt, pivot and MultiIndex

Lists, dummy columns and bins

Split list cells into rows with explode, build and reverse indicator columns with get_dummies and from_dummies, and group numbers with cut and qcut.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, pandas 3.0.6, numpy 2.5.3, pyarrow 25.0.1 and polars 1.44.2
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: Explode preserves each item and repeats its row · Indicator columns mark labels without adding observations · Choose fixed ranges or ranks for numeric values. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Split list-like cells into rows and account for their observation count
  • Create and reverse indicator columns for a single label
  • Choose fixed-range or quantile groups for numeric values
  • Track row labels and missing values through the three reshapes

Before you start

Three kinds of column hold several facts in one place, and pandas has a tool for each. Cells that hold lists, such as python;sql;excel, become one row per item: the pandas explode: A reshape that gives each item of a list-like cell its own row and repeats the other fields. The declared input policy determines how empty and missing lists contribute rows. method repeats the rest of the row for every item. A column of labels becomes dummy columns, one true-or-false indicator column: A column that marks whether each row has a specified label, often with Boolean values or zeros and ones. Several labels per observation can produce several marked columns. per label, with get_dummies, and from_dummies turns them back into one column. A column of numbers becomes bins, labelled ranges such as 18–25: cut uses edges you choose, while qcut puts the edges at quantile: A value selected or interpolated from ordered usable observations at a stated probability. Different conventions can return different values. More in “Descriptive statistics that do not mislead”, so that every bin holds about the same number of rows. These three reshapes come up in nearly every survey, job-board and customer analysis, for counting skills, preparing model inputs, or grouping ages and spending into bands.

This lesson works through twelve job applications, invented for it, with every run recorded: it explodes their skill lists and counts them per city, turns a label column into dummy columns and back, and bins the applicants’ ages both ways.

Explode preserves each item and repeats its row

Read A01’s three skills, then compare the twelve application rows with the number of listed items.

applications.csv CSV · applications.csv
id,city,age,source,skills
A01,Pune,22,portal,python;sql;excel
A02,Pune,29,referral,sql;excel
A03,Indore,18,portal,excel
A04,Indore,35,walk-in,python;sql
A05,Pune,41,portal,python;power bi
A06,Nagpur,26,referral,sql
A07,Nagpur,31,portal,python;sql;tableau
A08,Pune,24,walk-in,excel;power bi
A09,Indore,47,referral,sql;excel
A10,Nagpur,19,portal,python
A11,Pune,58,referral,excel;sql
A12,Indore,25,portal,python;excel

str.split(";") turns each skills cell into a Python list, and the pandas explode method then gives every list item its own row, copying the other columns:

Follow A01’s three skill items into three rows, then check the same expansion over all twelve applications.

Interactive · stage by stage List items change the row grain Follow A01's three items, then reconcile all 23 skill rows with twelve applications.
List items change the row grain, step 4 of 4. Showing One application row, Three listed skills, One row per item, Separate the counts. In focus: Separate the counts.One application rowA01 · PuneThree listed skillspython · sql · excelOne row per itemA01 repeated three timesSeparate the counts12 applications · 23 rows

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. Keep the originating identifier. A01 has one application row with a skills cell python;sql;excel.
  2. Split the cell. str.split(";") produces the three list items python, sql and excel.
  3. Give each item a row. explode repeats A01 and Pune on three item rows. The original row label is repeated unless a fresh index is requested.
  4. Reconcile the intended unit. The whole complete file has twelve applications and 23 listed items, so it becomes 23 skill rows without becoming 23 applicants.

The 23 skill rows remain twelve applications. Compare the repeated row labels with the fresh-index crosstab.

Twelve applications, 23 skill rows Python · explode_skills.py
import pandas as pd

# Twelve invented job applications; each lists its skills in one cell, separated by semicolons.
apps = pd.read_csv("applications.csv")

skills = apps.assign(skill=apps["skills"].str.split(";")).explode("skill")
print(skills[["id", "city", "skill"]].head(6))  # the index labels repeat: one row per list item
print(f"{len(apps)} applications -> {len(skills)} rows, one per skill")
print()

print(skills["skill"].value_counts())
print()

# Repeated index labels trip up anything that aligns on the index.
try:
    pd.crosstab(skills["city"], skills["skill"])
except ValueError as err:
    print(f"crosstab failed: {err}")

skills = apps.assign(skill=apps["skills"].str.split(";")).explode("skill", ignore_index=True)
print(pd.crosstab(skills["city"], skills["skill"]))  # applicants per city and skill

Output

    id    city   skill
0  A01    Pune  python
0  A01    Pune     sql
0  A01    Pune   excel
1  A02    Pune     sql
1  A02    Pune   excel
2  A03  Indore   excel
12 applications -> 23 rows, one per skill

skill
sql         7
excel       7
python      6
power bi    2
tableau     1
Name: count, dtype: int64

crosstab failed: cannot reindex on an axis with duplicate labels
skill   excel  power bi  python  sql  tableau
city                                         
Indore      3         0       2    2        0
Nagpur      0         0       2    2        1
Pune        4         2       2    3        0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 explode_skills.py

Lists of 3, 2, 1 … items gave 23 rows for 12 applications, and after the explode, counting skills is an ordinary value_counts. Look at the index of the first rows, though: 0, 0, 0, 1, 1. Explode keeps each row’s original label on all of its copies. Repeated labels can break an operation that requires unambiguous index alignment; here pd.crosstab refused with “cannot reindex on an axis with duplicate labels”. Passing ignore_index=True, or calling reset_index(drop=True) afterwards, gives the rows fresh labels, and the crosstab then shows the applicants per city and skill.

Two more behaviours matter once real data arrives. In pandas, an empty list explodes into one row holding NaN, so it is not lost silently. Several list columns explode together, item by item, when their lists have equal lengths in every row. Lists typed by people bring spaces, capitals and repeats as well, and each of those changes the counts.

Text to Columns Split a delimited column into separate columns or rows in the browser, to see what a split will produce.

Checkpoint

Check: List items and row labels

  1. One question The complete skills file has twelve applications with 23 total listed items. How many rows should its explode return?

    Type a number.

    Show the answer: One question

    Answer: 23 rows

    Each listed item contributes one row, while application identifiers repeat on those rows.

Indicator columns mark labels without adding observations

A model or a statistics routine usually needs numbers, not labels. get_dummies turns one label column into dummy columns, one per distinct label, each true where the row has that label:

Follow A01’s portal label into its marked source column, then recover the label from that row.

Interactive · stage by stage One label becomes one marked column Follow A01's source into three flags and back to the original label.
One label becomes one marked column, step 4 of 4. Showing One source label, Three source columns, Recover the label, Several labels per row. In focus: Several labels per row.One source labelA01: portalThree source columnsportal true · others falseRecover the labelsource: portalSeveral labels per rowpython · sql · excel

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. Read the single label. A01 source is portal; A02 source is referral. Each remains one application observation.
  2. Mark its source column. get_dummies marks source_portal true on A01 and its other two source indicators false.
  3. Reverse the indicators. from_dummies reads A01's single marked source column and restores portal in a source column.
  4. Distinguish multi-label flags. A01's skill indicators mark python, sql and excel in the same row; they do not create three application rows.

The source indicators still have one row per application. The skill indicators can mark several labels in that same row.

Indicators for one label and for several labels per cell Python · dummy_columns.py
import pandas as pd

apps = pd.read_csv("applications.csv")

# One label per row: one indicator column per label.
flags = pd.get_dummies(apps["source"], prefix="source")
print(apps[["id", "source"]].join(flags).head(4))
print(f"dtype of the indicators: {flags.dtypes.unique()[0]}")
print()

# And back: from_dummies finds the column that is True in each row.
print(pd.from_dummies(flags, sep="_").head(4))
print()

# Several labels per cell: str.get_dummies splits on the separator and marks every label it finds.
skill_flags = apps["skills"].str.get_dummies(sep=";")
print(skill_flags.head(4))
print()
print(skill_flags.sum().sort_values(ascending=False))  # the same counts as explode and value_counts

Output

    id    source  source_portal  source_referral  source_walk-in
0  A01    portal           True            False           False
1  A02  referral          False             True           False
2  A03    portal           True            False           False
3  A04   walk-in          False            False            True
dtype of the indicators: bool

     source
0    portal
1  referral
2    portal
3   walk-in

   excel  power bi  python  sql  tableau
0      1         0       1    1        0
1      1         0       0    1        0
2      1         0       0    0        0
3      0         0       1    1        0

excel       7
sql         7
python      6
power bi    2
tableau     1
dtype: int64

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dummy_columns.py

  • pd.get_dummies(apps["source"], prefix="source") made three dummy columns, source_portal, source_referral and source_walk-in, all of them bool.
  • pd.from_dummies(flags, sep="_") reversed them: in each row it found the one column that is true and wrote its label back into a source column.
  • For cells holding several labels, str.get_dummies(sep=";") splits on the separator and marks every label found, so one row can have several ones. Its column sums are the skill counts that explode and value_counts gave.

Two options are worth knowing by name. drop_first=True leaves out the first label’s column, because in a linear model the dropped label becomes the baseline that the others are compared with. dummy_na=True adds a column that marks missing values, which are otherwise all false.

Checkpoint

Check: Labels expressed as marked columns

  1. One question from_dummies reads A02's single true source_referral flag. Which source label does it restore?

    Choose one answer.

    Show the answer: One question

    Answer: referral

    The indicator table can represent one source label per row and reverse it without changing the application count.

Choose fixed ranges or ranks for numeric values

With specified edges, cut gives fixed ranges; qcut derives them from ranks in the input. Both turn numbers into categories:

Compare the same twelve sorted ages in the overview: the fixed ranges have 5, 4 and 3 rows, while these distinct-valued quartiles have four groups of 3.

Twelve ages binned two ways: cut with chosen edges gives bands of 5, 4 and 3 people; qcut with quartile edges gives four bands of 3.12 ages, sorted18 19 22 24 2526 29 31 3541 47 58cut: fixed edges17 · 25 · 35 · 60(17, 25]: 5(25, 35]: 4(35, 60]: 3qcut(q=4)quartile edges18 · 23.5 · 27.536.5 · 583 in each bandsizes varyedges move

Chosen ranges contain 5, 4 and 3 of these ages; their four distinct-valued quartile groups contain 3 each

Text description of the diagram

The top box lists the twelve applicants' ages in order: 18, 19, 22, 24 and 25; then 26, 29, 31 and 35; then 41, 47 and 58.

Two arrows lead down from it. The first, labelled "sizes vary", leads to cut with the edges 17, 25, 35 and 60. Its bands are (17, 25] with 5 applicants, (25, 35] with 4 and (35, 60] with 3.

The second arrow, labelled "edges move", leads to qcut with q=4. Its edges are the data's quartiles, 18, 23.5, 27.5, 36.5 and 58, and each of its four bands holds 3 applicants.

Interactive · stage by stage The same age under two different band definitions Compare the fixed ranges and the quartiles of the twelve recorded ages.
The same age under two different band definitions, step 4 of 4. Showing Twelve known ages, Fixed edges, Data-derived edges, Place age 25. In focus: Place age 25.Twelve known ages18 to 58Fixed edges17 · 25 · 35 · 60Data-derived edges23.5 · 27.5 · 36.5Place age 25fixed first · rank second

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. Keep the input unchanged. The twelve sorted ages are 18, 19, 22, 24, 25, 26, 29, 31, 35, 41, 47 and 58.
  2. Use chosen ranges. cut uses edges 17, 25, 35 and 60. Its right-closed bands contain five, four and three applicants.
  3. Use the observed quartiles. qcut computes the inner quartiles 23.5, 27.5 and 36.5. These distinct-valued samples give four groups of three.
  4. Compare the definitions. Age 25 is at the first fixed band's included right edge, but lies above the first quantile edge 23.5, so its rank group is second.

Follow age 25 into the right-closed interval: An interval that includes its upper endpoint. In (a, b], values equal to b are included and values equal to a are excluded. More in “Nearest-key and range joins: merge_asof” first fixed band. The quartile boundary of 23.5 puts it in the second rank group instead.

The same twelve ages, cut and qcut Python · age_bands.py
import pandas as pd

ages = pd.read_csv("applications.csv")["age"]
print(sorted(ages.tolist()))
print()

# cut: you choose the edges; each band includes its right edge, so (17, 25] holds the ages 18 to 25.
bands = pd.cut(ages, bins=[17, 25, 35, 60], labels=["18-25", "26-35", "36-60"])
print(bands.value_counts(sort=False))
print()

# qcut: four groups of (nearly) equal size; the edges come from the data's quartiles.
quartiles, edges = pd.qcut(ages, q=4, retbins=True)
print(quartiles.value_counts(sort=False))
print(f"quartile edges: {edges.tolist()}")

Output

[18, 19, 22, 24, 25, 26, 29, 31, 35, 41, 47, 58]

age
18-25    5
26-35    4
36-60    3
Name: count, dtype: int64

age
(17.999, 23.5]    3
(23.5, 27.5]      3
(27.5, 36.5]      3
(36.5, 58.0]      3
Name: count, dtype: int64
quartile edges: [18.0, 23.5, 27.5, 36.5, 58.0]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 age_bands.py

pd.cut(ages, bins=[17, 25, 35, 60], labels=[…]) used the bin edge: A boundary used to place a numeric value in a range. The interval convention states which endpoint belongs to each adjacent bin and how out-of-range or missing values are handled. as given. Each band includes its right edge, so (17, 25] holds the ages 18 to 25, and the bands hold 5, 4 and 3 applicants. pd.qcut(ages, q=4) computed the quartiles of these ages, 23.5, 27.5 and 36.5, and used them as edges, so each of the four groups holds exactly 3 applicants. retbins=True returned those edges, which you need if the same groups must be applied to other data later.

cut or qcut?
Criterion cutqcut
Edges Yours, or equal widthsQuantiles of the data
Sizes VaryAbout equal
Labels Fixed when edges are fixedRanks in this data
Known values NaN outside chosen edgesThe observed range is covered
When to choose Age, price or grade bandsQuartiles, deciles

The details that decide which band a value lands in also have names. include_lowest=True makes the first band include its left edge. right=False builds bands such as [18, 26), closed on the left instead of the right. duplicates="drop" lets qcut merge edges that coincide when many values are tied. Ranking orders values before forming groups. Polars offers these reshapes too; Polars 2 changes the empty-list default. bin_intervals uses specified breaks. bin_quantiles uses value quantiles. bin_ranks uses ranks.

Histogram Maker Bin a column of numbers into classes, and see which class a value on an edge goes into.

Checkpoint

Check: Fixed ranges and quantile groups

  1. One question The three fixed age ranges contain 5, 4 and 3 applicants. How many input ages do those counts reconcile?

    Type a number.

    Show the answer: One question

    Answer: 12 ages

    5 + 4 + 3 = 12, matching the complete input age column.

Messy list cells: counts that lie, and how to fix them Pro: opens with a Pro, Premium or Ultimate pass

Lists typed by people carry spaces, capitals, repeats and empty items, and each of them changes the counts without an error. This part reproduces every one on the same applications, measures its effect, and ends with an exercise.

In this part:

  • Count applicants after normalising skill items
  • Match paired list lengths before exploding
  • Practise: count skills per city

2 diagrams · 5 runnable examples (CSV, Python) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Binning without losing rows: edges, ties and moving quantiles Pro: opens with a Pro, Premium or Ultimate pass

An out-of-range cut value can become missing without an error, while recalculating quantile edges changes the numerical meaning of a label. This part reproduces both, with the counts, and shows how to keep bins comparable over time.

In this part:

  • Check boundary ages before trusting band counts
  • State which promise changes when quantiles tie
  • Freeze reference edges when labels compare periods

3 diagrams · 3 runnable examples (Python) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Explode and bins in polars, stable across versions Pro: opens with a Pro, Premium or Ultimate pass

Polars 2 changes how empty lists and binning are handled. Compare the pinned behaviour with the documented compatibility cases, then state a row and interval policy before moving between versions.

In this part:

  • Separate empty and null lists before row counting
  • Translate outer edges and interval closure deliberately
  • Keep the row policy explicit at version boundaries

3 diagrams · 2 runnable examples (Python) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: identify the reshape and its row unit

Use the list, label and numeric examples to distinguish observation rows from derived columns.

Practice that adapts to you

Practise this lesson

8 questions about 11 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 Three rows hold the lists ["a", "b"], ["c"] and ["d", "e", "f"]. How many rows does explode return?

    Type a number.

    Show the answer: Question 1 of 8

    Answer: 6 rows

    Every list item gets its own row: 2 + 1 + 3 = 6. The other columns, and the index label, are copied onto each of a row's items.

  2. Question 2 of 8 After df.explode("skill"), pd.crosstab(df["city"], df["skill"]) fails with "cannot reindex on an axis with duplicate labels". What fixes it?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: explode("skill", ignore_index=True), or reset_index(drop=True) after the explode

    explode copies each row's index label onto all of its items, so labels repeat, and crosstab aligns its inputs on the index. Fresh labels from ignore_index=True remove the repeats without removing any row; drop_duplicates would remove real rows.

  3. Question 3 of 8 pd.cut(ages, bins=[17, 25, 35, 60]): in which band does an applicant aged exactly 25 land?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: (17, 25], because each band includes its right edge

    cut builds right-closed intervals by default, so 25 belongs to (17, 25]. right=False would build [17, 25) and [25, 35), and 25 would move to the second band.

  4. Question 4 of 8 You need four customer groups of about the same size, from monthly spend. Which call fits?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: pd.qcut(spend, q=4)

    qcut puts the edges at the quartiles, so each group holds about a quarter of the customers. cut with bins=4 makes four bins of equal width, whose sizes depend on how the spending is spread; get_dummies would make a column per distinct amount.

  5. Question 5 of 8 A01 source is portal. Which source indicator is true in the recorded dummy table?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: source_portal

    One source label becomes one marked source column; the observation remains a single application row.

  6. Question 6 of 8 A01 skills are python;sql;excel. How many skill indicator columns are marked in its str.get_dummies row?

    Type a number.

    Show the answer: Question 6 of 8

    Answer: 3 columns

    The row lists three distinct skill labels, so python, sql and excel are marked without creating three observation rows.

  7. Question 7 of 8 For the twelve ages in applications.csv, how many values fall in the default interval (17, 25]?

    Type a number.

    Show the answer: Question 7 of 8

    Answer: 5 ages

    18, 19, 22, 24 and 25 fall above 17 and at or below 25.

  8. Question 8 of 8 What does pandas explode do with an empty list in the behaviour described by the lesson?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: It keeps one row with a missing item

    Count no-item applications according to the stated metric before deciding whether missing item rows should be removed.

Interview questions

  1. Fresher to mid · Warm-up

    What is the difference between cut and qcut in pandas?

    Both turn numbers into categories, but they choose edges differently. cut uses explicit edges or a number of equal-width bins across this input's range. Explicit edges keep an age range fixed; asking for four equal-width bins does not fix their numerical boundaries across files. qcut computes sample quantiles, so groups have about equal counts when the values allow distinct edges, and their boundaries change with the input. Choose fixed cut edges for comparable age or price bands and qcut for relative quartiles or deciles. cut includes right edges by default; tied quantiles can make qcut refuse, while duplicates="drop" returns fewer groups.

  2. Mid to senior

    A dashboard counts demand for skills from the semicolon-separated skills column of job applications. The week the application form changed from a free-text box to checkboxes, demand for SQL rose 30 %. What do you check before believing it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    Marketing labels customers top, high, mid and low every month with pd.qcut(spend, 4). After a slow month, the top group includes customers who spend half of what top customers spent a month earlier, and the campaign underperforms. Explain what happened, and design labels that stay comparable from month to month.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    You must turn a city column with 400 distinct values into inputs for a linear regression on 1 million rows. What does pd.get_dummies produce, what goes wrong, and what would you do instead?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • str.split and explode give each list item its own row. Explode repeats the index labels, so use ignore_index=True before anything that aligns on the index.
  • get_dummies turns a label column into indicator columns and from_dummies reverses it; str.get_dummies(sep=…) handles several labels per cell.
  • cut bins numbers by edges you choose and includes each right edge; qcut puts the edges at quantiles, so its bins hold about equal numbers of rows.
  • retbins=True returns the edges, so that the same bins can be applied to other data.

Terms in this lesson

bin edge
A boundary used to place a numeric value in a range. The interval convention states which endpoint belongs to each adjacent bin and how out-of-range or missing values are handled.
explode
A reshape that gives each item of a list-like cell its own row and repeats the other fields. The declared input policy determines how empty and missing lists contribute rows.
indicator column
A column that marks whether each row has a specified label, often with Boolean values or zeros and ones. Several labels per observation can produce several marked columns.

Corrections

  • Added figures and checks on the recorded applications; clarified missing-row controls, interval coverage and comparable quantile labels.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.