Banned Book Persistence Project:¶

Do historically persistent banned books continue to be targeted in the modern wave of school bans, and where is that activity concentrated?¶

By Anjelica Martinez

About the Project and Data

I started this project out of curiosity over the nature of book bans and whether there were any patterns in the type of books that are banned. Are they specific titles that persist over decades? Do the bans focus around subject matter and themes?

I was hoping to review as much data as possible stretching back to the 1900s but unfortunately there is only data for the past 3 decades (1990s, 2000s, 2010s) since the American Library Association's (ALA): Office for Intellectual Freedom, a non-profit organization based in the United States that tracks and reports on book challenges and censorship attempts in U.S. schools and libraries, only began documenting book bans in 1990. The organization gathers its data from confidential reporting from library professionals, community members, and nationally published news stories. Hence, it only shows a snapshot of the book bans that are happening across the nation's libraries.

In [1]:
import pandas as pd
import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt
import seaborn as sns

PATH = "../data/ala_challenged_books.csv"

df = pd.read_csv(PATH)

df.head()
Out[1]:
title alt-title author(s) decade source notes
0 The Absolutely True Diary of a Part-Time Indian NaN Sherman Alexie 2010s ALA_OIF NaN
1 Captain Underpants NaN Dav Pilkey 2010s ALA_OIF series
2 Thirteen Reasons Why NaN Jay Asher 2010s ALA_OIF NaN
3 Looking for Alaska NaN John Green 2010s ALA_OIF NaN
4 Melissa George Alex Gino 2010s ALA_OIF Title renamed in later editions
In [2]:
df.info()
df.head(10)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 30 entries, 0 to 29
Data columns (total 6 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   title      30 non-null     object
 1   alt-title  1 non-null      object
 2   author(s)  30 non-null     object
 3   decade     30 non-null     object
 4   source     30 non-null     object
 5   notes      9 non-null      object
dtypes: object(6)
memory usage: 1.5+ KB
Out[2]:
title alt-title author(s) decade source notes
0 The Absolutely True Diary of a Part-Time Indian NaN Sherman Alexie 2010s ALA_OIF NaN
1 Captain Underpants NaN Dav Pilkey 2010s ALA_OIF series
2 Thirteen Reasons Why NaN Jay Asher 2010s ALA_OIF NaN
3 Looking for Alaska NaN John Green 2010s ALA_OIF NaN
4 Melissa George Alex Gino 2010s ALA_OIF Title renamed in later editions
5 And Tango Makes Three NaN Justin Richardson and Peter Parnell 2010s ALA_OIF NaN
6 Drama NaN Raina Telgemeier 2010s ALA_OIF NaN
7 Fifty Shades of Grey NaN E. L. James 2010s ALA_OIF NaN
8 Internet Girls NaN Lauren Myracle 2010s ALA_OIF series
9 The Bluest Eye NaN Toni Morrison 2010s ALA_OIF NaN
In [3]:
print("Total rows:", len(df))
print("Unique titles:", df["title"].unique())
print("Decades", sorted(df["decade"].unique()))
Total rows: 30
Unique titles: ['The Absolutely True Diary of a Part-Time Indian' 'Captain Underpants'
 'Thirteen Reasons Why ' 'Looking for Alaska' 'Melissa'
 'And Tango Makes Three' 'Drama' 'Fifty Shades of Grey' 'Internet Girls'
 'The Bluest Eye' 'Harry Potter' 'Alice series ' 'The Chocolate War'
 'Of Mice and Men' 'I Know Why the Caged Bird Sings' 'Scary Stories'
 'His Dark Materials' 'ttyl; ttfn; l8r g8r'
 'The Perks of Being a Wallflower' 'Daddy’s Roommate'
 'The Adventures of Huckleberry Finn' 'Forever' 'Bridge to Terabithia'
 'Heather Has Two Mommies' 'The Catcher in the Rye']
Decades ['1990s', '2000s', '2010s']
In [4]:
persistence = (
    df.groupby("title", as_index=False)
    .agg(
        n_decades=("decade", "nunique"),
        decades=("decade", lambda s: ", ".join(sorted(set(s)))),
        author=("author(s)", lambda s: s.dropna().iloc[0])
    )
    .sort_values(["n_decades", "title"], ascending=[False, True])
    )

persistence
Out[4]:
title n_decades decades author
1 And Tango Makes Three 2 2000s, 2010s Justin Richardson and Peter Parnell
11 I Know Why the Caged Bird Sings 2 1990s, 2000s Maya Angelou
15 Of Mice and Men 2 1990s, 2000s John Steinbeck
16 Scary Stories 2 1990s, 2000s Alvin Schwartz
21 The Chocolate War 2 1990s, 2000s Robert Cormier
0 Alice series 1 2000s Phyllis Reynolds Naylor
2 Bridge to Terabithia 1 1990s Katherine Paterson
3 Captain Underpants 1 2010s Dav Pilkey
4 Daddy’s Roommate 1 1990s Michael Willhoite
5 Drama 1 2010s Raina Telgemeier
6 Fifty Shades of Grey 1 2010s E. L. James
7 Forever 1 1990s Judy Blume
8 Harry Potter 1 2000s J.K. Rowling
9 Heather Has Two Mommies 1 1990s Leslea Newman
10 His Dark Materials 1 2000s Philip Pullman
12 Internet Girls 1 2010s Lauren Myracle
13 Looking for Alaska 1 2010s John Green
14 Melissa 1 2010s Alex Gino
17 The Absolutely True Diary of a Part-Time Indian 1 2010s Sherman Alexie
18 The Adventures of Huckleberry Finn 1 1990s Mark Twain
19 The Bluest Eye 1 2010s Toni Morrison
20 The Catcher in the Rye 1 1990s J.D. Salinger
22 The Perks of Being a Wallflower 1 2000s Stephen Chbosky
23 Thirteen Reasons Why 1 2010s Jay Asher
24 ttyl; ttfn; l8r g8r 1 2000s Lauren Myracle
In [5]:
persistence.head()
Out[5]:
title n_decades decades author
1 And Tango Makes Three 2 2000s, 2010s Justin Richardson and Peter Parnell
11 I Know Why the Caged Bird Sings 2 1990s, 2000s Maya Angelou
15 Of Mice and Men 2 1990s, 2000s John Steinbeck
16 Scary Stories 2 1990s, 2000s Alvin Schwartz
21 The Chocolate War 2 1990s, 2000s Robert Cormier

Part 1: Which book(s) keeps showing up across decades?¶

The ALA compiles yearly lists of the top 10 banned books. To make my analysis more concise, I focused on the decades data that compiles the top 100 books of that particular decade (i.e. 1990s). From each decade I took the top 10 banned books and then put together all three decades into one dataset. From there, I tallied up the books to find which books came up the most on the lists. This landed me at the following books:

Most persistent books across decades

  1. And Tango makes three by Justin Richardson and Peter Parnell

  2. I Know Why the Caged Bird Sings by Maya Angelou

  3. Of Mice and Men by John Steinback

  4. Scary Stories by Alvin Schwartz

  5. The Chocolate War by Robert Cormier

These five book titles appear across two decades, while the remaining only appear in one decade. It could mean that the books being challenged are a specific small group of titles rather than a rotation of books.

In [6]:
persistent_books = persistence[persistence["n_decades"] > 1].copy()

persistent_books
Out[6]:
title n_decades decades author
1 And Tango Makes Three 2 2000s, 2010s Justin Richardson and Peter Parnell
11 I Know Why the Caged Bird Sings 2 1990s, 2000s Maya Angelou
15 Of Mice and Men 2 1990s, 2000s John Steinbeck
16 Scary Stories 2 1990s, 2000s Alvin Schwartz
21 The Chocolate War 2 1990s, 2000s Robert Cormier
In [7]:
persistent_books[["title", "author", "n_decades", "decades"]]
Out[7]:
title author n_decades decades
1 And Tango Makes Three Justin Richardson and Peter Parnell 2 2000s, 2010s
11 I Know Why the Caged Bird Sings Maya Angelou 2 1990s, 2000s
15 Of Mice and Men John Steinbeck 2 1990s, 2000s
16 Scary Stories Alvin Schwartz 2 1990s, 2000s
21 The Chocolate War Robert Cormier 2 1990s, 2000s
In [8]:
persistent_books["theme"] = [
    "LGBTQ+ representation",
    "Race, autobiography",
    "Violence, classic literature",
    "Violence, fear",
    "Authority, sexuality"]

persistent_books["theme_source"] = ("ALA challenge descriptions and publisher summaries")
In [9]:
persistent_books
Out[9]:
title n_decades decades author theme theme_source
1 And Tango Makes Three 2 2000s, 2010s Justin Richardson and Peter Parnell LGBTQ+ representation ALA challenge descriptions and publisher summa...
11 I Know Why the Caged Bird Sings 2 1990s, 2000s Maya Angelou Race, autobiography ALA challenge descriptions and publisher summa...
15 Of Mice and Men 2 1990s, 2000s John Steinbeck Violence, classic literature ALA challenge descriptions and publisher summa...
16 Scary Stories 2 1990s, 2000s Alvin Schwartz Violence, fear ALA challenge descriptions and publisher summa...
21 The Chocolate War 2 1990s, 2000s Robert Cormier Authority, sexuality ALA challenge descriptions and publisher summa...

Part 2: Theme/Topic Patterns¶

When we look at the book themes we see a pattern taking place. For these 5 books that are repeated twice within the 90s, 2000s, and 2010s decades the themes are:

Banned Book Themes

  • LGTBQ+ representation
    • And Tango Makes Three:
  • Race and Marginalization:
    • I Know Why The Caged Bird Sings:
  • Youth perspective, involve violence, fear, or challenges to authority:
    • Of Mice and Men
    • Scary Stories
    • The Chocolate War

Now that we know the top 5 books that persist across decades, I wanted to find out of these books, how many times do they appear in the 2020s specifically.

In [10]:
persistent_books.to_csv(
    "../data/persistent_books_themes.csv",
    index=False
)

Which books persist across decades, and where are those same books being banned during the contemporary surge in bans (2021–2025)?

In [11]:
pen_2122 = pd.read_csv("../data/pen_book_bans_2021-2022.csv")
pen_2223 = pd.read_csv("../data/pen_book_bans_2022-2023.csv")
pen_2324 = pd.read_csv("../data/pen_book_bans_2023-2024.csv")
pen_2425 = pd.read_csv("../data/pen_book_bans_2024-2025.csv")

# Adds a source period
pen_2122["reporting_period"] = "2021-2022"
pen_2223["reporting_period"] = "2022-2023"
pen_2324["reporting_period"] = "2023-2024"
pen_2425["reporting_period"] = "2024-2025"
In [12]:
pen = pd.concat(
    [pen_2122, pen_2223, pen_2324, pen_2425],
    ignore_index=True
)

pen.info()
pen["reporting_period"].value_counts()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 22659 entries, 0 to 22658
Data columns (total 16 columns):
 #   Column                     Non-Null Count  Dtype 
---  ------                     --------------  ----- 
 0   Author                     22658 non-null  object
 1   Title                      22658 non-null  object
 2   Type of Ban                2532 non-null   object
 3   Secondary Author(s)        1287 non-null   object
 4   Illustrator(s)             1724 non-null   object
 5   Translator(s)              211 non-null    object
 6   State                      22659 non-null  object
 7   District                   22659 non-null  object
 8   Date of Challenge/Removal  12613 non-null  object
 9   Origin of Challenge        5894 non-null   object
 10  reporting_period           22659 non-null  object
 11  Series Name                4138 non-null   object
 12  Ban Status                 20127 non-null  object
 13  Challenge/Removal          10046 non-null  object
 14  Initiating Action          10046 non-null  object
 15  Series                     2205 non-null   object
dtypes: object(16)
memory usage: 2.8+ MB
Out[12]:
reporting_period
2023-2024    10046
2024-2025     6719
2022-2023     3362
2021-2022     2532
Name: count, dtype: int64
In [13]:
pen["Title"].value_counts().head(10)
Out[13]:
Title
Looking for Alaska                 147
Nineteen Minutes                   142
Sold                               136
The Perks of Being a Wallflower    135
Thirteen Reasons Why               126
Crank                              116
The Bluest Eye                     116
The Kite Runner                    115
Tricks                             112
Identical                          110
Name: count, dtype: int64
In [14]:
pen["State"].value_counts().head(10)
Out[14]:
State
Florida         8837
Iowa            3798
Texas           3745
Tennessee       2016
Pennsylvania     737
Nation           590
Wisconsin        482
Missouri         419
Utah             351
Virginia         312
Name: count, dtype: int64
In [15]:
target_titles = persistent_books["title"].tolist()

pen_persistent = pen[pen["Title"].isin(target_titles)].copy()

len(pen_persistent)
Out[15]:
43
In [16]:
pen_persistent["Title"].value_counts()
Out[16]:
Title
And Tango Makes Three              20
The Chocolate War                  10
I Know Why the Caged Bird Sings     8
Of Mice and Men                     5
Name: count, dtype: int64
In [17]:
ban_counts_2020s = (
    pen_persistent["Title"]
    .value_counts()
    .reset_index()
)

ban_counts_2020s.columns = ["Book Title", "Number of Bans (2021-2025)"]

ban_counts_2020s
Out[17]:
Book Title Number of Bans (2021-2025)
0 And Tango Makes Three 20
1 The Chocolate War 10
2 I Know Why the Caged Bird Sings 8
3 Of Mice and Men 5

When we take our top 5 books that are seen more than twice in the three decades, we find that one of the books, Scary Stories to Tell in the Dark is missing from the line up. This could be related to society being more concerned with other topics that challenge social values or identity rather than books, like Scary Stories, being too scary for children to read vs books that challenge social values and identity.

Top 4 Persistent Banned Books in 2021-2025

  • And Tango Makes Three
  • The Chocolate War
  • I Know Why the Caged Bird Sings
  • Of Mice and Men

To better understand why these books consistently appear in the banned books list I dove into what each book is about and what themes are seen in the book and link to the reasons they were banned and what that says about our society.

And Tango Makes Three is a children's book about a same-sex family of penguins. The controversy here is that it challenges the norm of a heterosexual couples and shows at early childhood that same-sex families exist. It reflects the continued discomfort that LGTBQ+ is being normalized and that it's ok.

The Chocolate War is a book that is anti-authority, anti-conformity and anti-moral-compliance and resists the idea that youth literature should reinforce instritutional authority. For schools that value discipline, hierarchy and rule-following this causes a disruptive message.

I Know Why The Caged Bird Sings this book forces confrontation with system racism and sexual abuse, taboo topics that are continously labelled as inappropriate. It shows a discomfort with the teaching and understanding of racial trauma.

Of Mice and Men shows how harsh and unfair society can be instead of telling a hopeful story about things getting better. Many find this topic uncomfortable and would rather not expose students to this type of material.

As we can see, each of these books challenges core norms around family, authority, race and identity that school institutions find threatening in modern times.

In case you were wondering, the Scary Stories to Tell in The Dark series was banned for being considered "too scary" for elementary and middle school students due to the graphic illustrations (Stephen Gammel's artwork was even considered disturbing to many adults), the stories causing nightmares (fear/anxiety), and the supernatural and macabre themes (ghosts, death, gore) it centered around.
In [18]:
pen_persistent["State"].value_counts()
Out[18]:
State
Florida           19
Iowa              13
Tennessee          4
Texas              3
Oklahoma           1
Pennsylvania       1
Wisconsin          1
North Carolina     1
Name: count, dtype: int64

Florida (19 bans) and Iowa (13 bans) are the states with the most persistent book bans (2021-2025). When we think of book bans we think of them being a national phenemenon but it looks like it's actually only a few states that are pushing this. Why these states? You would think it might be because there are more people that are objecting over certain titles but after some digging I discovered it's actually because these states have policies in place that make it easier to ban books and easier to spread across school systems.

In [19]:
state_counts = (
    pen_persistent["State"]
    .value_counts()
    .reset_index()
)

state_counts.columns = ["state", "ban_count"]

state_counts
Out[19]:
state ban_count
0 Florida 19
1 Iowa 13
2 Tennessee 4
3 Texas 3
4 Oklahoma 1
5 Pennsylvania 1
6 Wisconsin 1
7 North Carolina 1
In [20]:
# Load state geometry
states = gpd.read_file("https://www2.census.gov/geo/tiger/GENZ2021/shp/cb_2021_us_state_20m.zip")

# Keep contiguous US only
states = states[~states["NAME"].isin(
   [ "Alaska", "Hawaii", "Puerto Rico"]
)]

states[["NAME", "geometry"]].head()
Out[20]:
NAME geometry
0 Louisiana POLYGON ((-94.04305 32.69303, -94.04303 32.797...
2 Maryland MULTIPOLYGON (((-76.04621 38.02553, -76.00734 ...
3 Wisconsin MULTIPOLYGON (((-86.93428 45.42115, -86.83575 ...
4 Florida MULTIPOLYGON (((-81.81169 24.56874, -81.75127 ...
5 Georgia POLYGON ((-85.60516 34.98468, -85.47434 34.983...
In [21]:
geo = states.merge(
    state_counts,
    left_on="NAME",
    right_on="state",
    how="left"
)

geo["ban_count"] = geo["ban_count"].fillna(0)

geo[["NAME", "ban_count"]].sort_values("ban_count", ascending=False).head(10)
Out[21]:
NAME ban_count
3 Florida 19.0
7 Iowa 13.0
5 Tennessee 4.0
13 Texas 3.0
2 Wisconsin 1.0
35 Pennsylvania 1.0
36 Oklahoma 1.0
37 North Carolina 1.0
38 Massachusetts 0.0
29 Kansas 0.0
In [26]:
fig, ax = plt.subplots(1, 1, figsize=(12, 8))

geo.plot(
    column="ban_count",
    ax=ax,
    legend=True,
    cmap="Blues",
    vmin=0,
    vmax=20,  # or np.percentile(geo["ban_count"], 95)
    edgecolor="black",
    linewidth=0.3
)

ax.set_title("Modern bans of historically persistent books by state (PEN America)",
             fontsize=14)
ax.axis("off")
plt.tight_layout()
plt.show()
No description has been provided for this image

Which titles drive bans in high-activity states?

Florida leads with the most bans of the And Tango Makes Three (LGTBQ+ representative book), whereas Iowa has the highest bans with the book The Chocolate War (anti-authority), Florida also has the highest bans of I Know Why The Caged Bird Sings (systemic racism and sexual abuse).

It appears that different regions target different specific themes. This could further imply that book bans are shaped by local priorities rather than a single national agenda.

In [28]:
# Creates table with the count of banned books by state
state_title_counts = (
    pen_persistent
    .groupby(["State", "Title"], as_index=False)
    .size()
    .rename(columns={"size":"ban_count"})
    )

# Sorts table in ascending order
state_title_counts.sort_values("ban_count", ascending=False)
Out[28]:
State Title ban_count
0 Florida And Tango Makes Three 12
7 Iowa The Chocolate War 6
4 Iowa And Tango Makes Three 4
3 Florida The Chocolate War 3
1 Florida I Know Why the Caged Bird Sings 3
12 Tennessee I Know Why the Caged Bird Sings 2
5 Iowa I Know Why the Caged Bird Sings 2
16 Texas Of Mice and Men 1
15 Texas I Know Why the Caged Bird Sings 1
14 Texas And Tango Makes Three 1
13 Tennessee Of Mice and Men 1
9 Oklahoma Of Mice and Men 1
11 Tennessee And Tango Makes Three 1
10 Pennsylvania And Tango Makes Three 1
8 North Carolina And Tango Makes Three 1
6 Iowa Of Mice and Men 1
2 Florida Of Mice and Men 1
17 Wisconsin The Chocolate War 1
In [32]:
top_states = ["Florida", "Iowa", "Tennessee", "Texas"]

plot_data = state_title_counts[
    state_title_counts["State"].isin(top_states)
]

plt.figure(figsize=(10, 6))

sns.barplot(
    data=plot_data,
    x="State",
    y="ban_count",
    hue="Title",
    palette="Set2"
)

plt.title("Bans of Historically Persistent Books by State (2021-2025)")
plt.ylabel("Number of times banned")
plt.xlabel("State")
plt.legend(title="Book Title", bbox_to_anchor=(1.05, 1), loc="upper left")
plt.tight_layout()
plt.show()
No description has been provided for this image