Hugging Face, End to EndLesson 39 of 75

Contents

Hugging Face, End to End

Lesson 39 of 75. Sign in to keep your place.

Module 5 · Datasets, and the work that happens before any model

A dataset that behaves like a list and is not one

9 min read

Loading, and what comes back

Python
from datasets import load_dataset

ds = load_dataset("imdb")
print(ds)
Text
DatasetDict({
    train: Dataset({features: ['text', 'label'], num_rows: 25000}),
    test:  Dataset({features: ['text', 'label'], num_rows: 25000}),
    unsupervised: Dataset({features: ['text', 'label'], num_rows: 50000}),
})

A DatasetDict is a dictionary of splits. Each Dataset has features (named, typed columns) and a row count. Ask for one split directly and you get the Dataset alone:

Python
train = load_dataset("imdb", split="train")
print(train[0])                    # one row, as a dict
print(train[:3]["text"][0][:80])   # a slice returns a dict of lists
print(train.features)

That last line is worth internalising. Indexing a row gives a dict; slicing gives a dict of lists. train[0] is {'text': ..., 'label': 1}; train[:3] is {'text': [...], 'label': [...]}. Half the confusing errors in a first hour with this library come from expecting a list of dicts.

Features are typed, and the types do work

Python
print(train.features["label"])
# ClassLabel(names=['neg', 'pos'], id=None)

print(train.features["label"].int2str(1))   # 'pos'

ClassLabel carries the names, so you never have to guess whether 1 means positive: the same problem id2label solves on the model side. Other types you will meet are Value("string"), Value("int32"), Sequence, Image, and Audio, and the last two decode lazily, which the media lesson returns to.

Split slicing without writing Python

Python
small = load_dataset("imdb", split="train[:1%]")
middle = load_dataset("imdb", split="train[100:200]")
both  = load_dataset("imdb", split="train+test")

train[:1%] is the habit worth forming. Develop on one per cent, then remove the suffix. A pipeline debugged on 250 rows runs unchanged on 25,000, and you will have saved yourself ten minutes per mistake.

Splitting when the dataset has no split

Python
parts = ds["train"].train_test_split(test_size=0.1, seed=42, stratify_by_column="label")

Three things here are deliberate. seed makes it reproducible, so the numbers you quote next week come from the same split. stratify_by_column keeps class proportions equal across the two halves, which matters enormously when a class is rare: an unstratified 10% test split of a dataset with 2% positives can easily land with almost none, and your recall figure then measures nothing.

And a validation set is a third split, not a second use of the test set:

Python
tmp = ds["train"].train_test_split(test_size=0.2, seed=42)
val_test = tmp["test"].train_test_split(test_size=0.5, seed=42)
# tmp['train'] = 80% train, val_test['train'] = 10% val, val_test['test'] = 10% test

You tune on validation. You touch test once, at the end. Every time you look at the test score and change something, it becomes a little more like a training set, and the number it reports becomes a little more optimistic than reality.

Loading your own files

No upload required: load_dataset takes formats directly:

Python
ds = load_dataset("csv", data_files="reviews.csv")
ds = load_dataset("json", data_files={"train": "tr.jsonl", "test": "te.jsonl"})
ds = load_dataset("parquet", data_files="data/*.parquet")
ds = load_dataset("imagefolder", data_dir="photos/")   # labels from folder names

So the whole library is available for local work. You do not need to publish anything to get streaming, mapping, caching and typed columns.

Configurations, when one name holds many datasets

Some repositories hold several variants, and the second positional argument chooses between them:

Python
from datasets import get_dataset_config_names
print(get_dataset_config_names("mozilla-foundation/common_voice_17_0")[:5])

hi = load_dataset("mozilla-foundation/common_voice_17_0", "hi", split="train")

For multilingual corpora the configuration is usually the language, and loading without it either fails with a list of options or silently gives you the default, which, more often than is comfortable, is English. Print the configuration names before you assume.

Sorting, shuffling, selecting

Python
ds = ds.shuffle(seed=42)
small = ds.select(range(1000))
pos = ds.filter(lambda r: r["label"] == 1)
ds = ds.remove_columns(["unused"]).rename_column("text", "sentence")

None of these copy the data. select and shuffle build an indices map (a list of row positions) and the underlying table stays where it is. This is why shuffling a 40 GB dataset is instant and takes no memory, and it is a genuine surprise the first time.

The exception is filter, which has to read every row to decide. On a large dataset, pass num_proc to parallelise it.

Do this now

Load any dataset with split="train[:1%]", print features, print row zero, and print a slice of three. Then run train_test_split with a seed and check that running it twice gives the same first row. That reproducibility is what makes every later comparison meaningful.

Every lesson here is open. Your progress is recorded once you pass the Image Generation, in Practice exam.

Check your understanding

A team splits a dataset with 2% positive examples into 90/10 train and test without stratifying, and their reported recall swings wildly between runs with different seeds. Why?

Pick the one you would defend. Your answer stays private.

Next lesson · 8 minWhy a 300 GB dataset opens instantly

Stuck on something?Ask Arjun, your AI tutor

How to cite: Addaly (2026) ‘A dataset that behaves like a list and is not one’, in Hugging Face, End to End. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/hugging-face/the-datasets-library (Accessed: 13 October 2026).