Skip to content

Bamboo Weekly #34: House of Representatives (solutions)

Get better at: CSV files, memory optimization, grouping, stack/unstack, window functions, and sorting.

Bamboo Weekly #34: House of Representatives (solutions)

This week, the US House of Representatives ousted its speaker, Kevin McCarthy, from office. What will happen next isn’t quite obvious, and probably isn’t a good omen for the Republican party, for the House, for the US government, and quite possibly even the world.

But hey, we’ll leave politics to other people. Here at Bamboo Weekly, we’re all about the data! And this week, we looked at election data about the House of Representatives.

From DALL-E: “Americans voting for members of Congress”

Data and seven questions

This week's data comes from the MIT Election Lab (https://electionlab.mit.edu/), run by Professor Charles Stewart III. The data itself comes in CSV format, downloadable from the site

https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/IG0UN2

We’re looking at the CSV file that you can get by retrieving the file called 1976-2022-house.tab. You can download this data in a variety of formats; I chose the comma-separated values (CSV), but other options are available.

You’ll also want to look at the codebook (aka “data dictionary”), downloadable from the same page in Markdown format.

I had seven questions and tasks for you about this week’s data. Without further ado, let’s take a look at them:

Load the data into a Pandas data frame. We only need the columns `year`, `state`, `state_po`, `district`, `stage`, `candidate`, `party`, `candidatevotes`, and `totalvotes`.

The first thing we’ll need to do is load up Pandas:

import pandas as pd

After having done that, we’ll want to import the CSV file into a data frame. I asked you to only load a handful of the columns, so we’ll use the “use_cols” keyword argument to read_csv:

filename = '/Users/reuven/Downloads/1976-2022-house.csv'
df = pd.read_csv(filename,
                usecols=['year', 'state', 'state_po', 
                         'district', 'stage', 'candidate', 
                         'party', 'candidatevotes', 'totalvotes'])

I forgot to mention in my original question that we only want to keep the general-election results around, dropping any from the primaries. We can see how many results there are for each with value_counts:

df['stage'].value_counts()

The result I get:

stage
GEN    32392
PRI       60
Name: count, dtype: int64

So most of the results are indeed from a general election. But let’s get rid of those that have to do with primaries. We can do that by creating a boolean series based on comparing the “stage” column with the string “GEN”. We can then feed that boolean series to “loc”. Only those rows containing GEN for “stage” will then be returned. We can then assign the result back to df:

df = df.loc[df['stage'] == 'GEN']

We can combine all of the above into a single chained query as follows:

df = (
    pd.read_csv(filename,
                usecols=['year', 'state', 'state_po', 
                         'district', 'stage', 'candidate', 
                         'party', 'candidatevotes', 'totalvotes'])
    .loc[lambda df_: df_['stage'] == 'GEN']
    .drop('stage', axis='columns')
)

You might notice some subtle differences between what I did above, and what I did earlier.

First, my call to “loc” no longer puts a boolean series inside of the square brackets. Rather, I pass a lambda (i.e., an anonymous function) that takes a single argument, which we call “df_” to indicate that it’s a temporary, local data frame — in this case, the data frame that we got back from read_csv.

We then run our comparison between the “stage” column on that temporary data frame and “GEN”, keeping only those for which it returns True.

Once we’ve used the “stage” column, we really don’t need it an more. So I invoke “drop” on the data frame, removing the “stage” column. We need to indicate that we’re working with columns, rather than rows, via the “axis” keyword argument.

I end up with a data frame of 32,392 rows and eight columns. I’ll add that it is a bit silly to have both the “state” column (with the state name) and the “state_po” column (with the state two-letter abbreviation). If I were doing this for myself, I’d probably just use “state_po”, since it uses less space and memory. But since not everyone reading BW is intimately familiar with all 50 US state names, let alone their two-letter abbreviations, I decided to stick with the longer values.

I’ll add, by the way, that the total memory used by this data frame on my system is 9.2 MB. Both “state” and “state_po” contain repeated strings, which are perfect candidates for Pandas categories — basically, the equivalent of enums. I can convert them both:

df['state'] = df['state'].astype('category')
df['state_po'] = df['state_po'].astype('category')

After executing the above, the memory footprint of the data frame drops to 5.4 MB, or about 60% of the original size. That’s right — with only two calls to “astype” and less than one second of execution time, we’ve cut our memory usage nearly in half. That’s a pretty big win, I’d say.

How many districts are there per state in 2022? What 10 states have the most districts?

The whole idea of the House of Representatives is that there should be one representative for every X people. (The number represented by X has changed over the years. Someone recently suggested that it be changed to 44 billion, but I don’t think that’ll work out very well.)

From this data, can we find out how many districts are in each state?

First, we need to find all of the rows from the year 2022. We have a “year” column, and could theoretically create a boolean series that compares the year with 2022. Then we could pass that boolean series to “loc”, and get back only those matching rows.

However, this is the sort of case that I think calls for the use of “set_index”: I can set the index to be the year, and then retrieve those rows that are from the year I want. The effect is the same, but it feels a bit easier and clearer to me. Because set_index, like so many methods in Pandas, returns a new data frame (but doesn’t affect the original one), we can use it in a chained set of methods:

(
    df
    .set_index('year')
    .loc[2022]
)

With this in place, we can ask the question: How many districts are there per state? Once we have a “per state” question, that means we’ll almost certainly need to use “groupby”. Grouping means that we want to run an aggregation method for each distinct value in a categorical column. We can thus say:

(
    df
    .set_index('year')
    .loc[2022]
    .groupby('state')['district']
)

In other words: For each distinct state, we want to get the value of “district”. However, if we do this, we’ll get a warning from Pandas, telling us that currently a “groupby” on a categorical dtype will give us a result for every element in the category, whether it’s observed or not. This default will be changing in a future version of Pandas to True, such that only observed values in the category will be shown. We can silence this error by passing observed=False. (In our case, because we created the category based on actual data, there isn’t any such thing as a non-observed category member, so it doesn’t really matter that much.)

We can thus say:

(
    df
    .set_index('year')
    .loc[2022]
    .groupby('state', observed=False)['district']
)

Now we need to apply an aggregation method. Which one will we use? I originally thought that I would use “count”, since I want to know how many there are. Then I can use “sort_values” to list the states, and how many congressional districts they each have. And then I can use “head” to get the top 10:

(
    df
    .set_index('year')
    .loc[2022]
    .groupby('state', observed=False)['district']
    .count()
    .sort_values(ascending=False)
    .head(10)
)

Doing this gives me some weird results, though:

state
NEW YORK         158
CALIFORNIA       104
TEXAS             93
FLORIDA           72
NEW JERSEY        53
MICHIGAN          51
ILLINOIS          46
TENNESSEE         37
MASSACHUSETTS     36
VIRGINIA          35
Name: district, dtype: int64

I’m no expert, but I’m pretty sure that New York does not have more representatives in Congress than California. And if we add the numbers from New York, California, Texas, as Florida together, we get 427, which is just under the total number of members of Congress. So … what’s going on here?

We basically counted how many candidates there were running for Congress in each state, not how many districts there were! That’s because this data set lists all candidates, not only the winners.

We’ll need another way to find this, and I decided the easiest would be to use the “max” aggregate method. After all, if the districts are numbered, and if this data set lists them, then we should be fine:

(
    df
    .set_index('year')
    .loc[2022]
    .groupby('state', observed=False)['district']
    .max()
    .sort_values(ascending=False)
    .head(10)
)

Sure enough, we get:

state
CALIFORNIA        52.0
TEXAS             38.0
FLORIDA           28.0
NEW YORK          26.0
PENNSYLVANIA      17.0
ILLINOIS          17.0
OHIO              15.0
GEORGIA           14.0
NORTH CAROLINA    14.0
MICHIGAN          13.0
Name: district, dtype: float64

This makes much more sense! We see that California has the most representatives, followed by Texas, Florida, and New York. What about the bottom-most states? We can get those with:

(
    df
    .set_index('year')
    .loc[2022]
    .groupby('state', observed=False)['district']
    .max()
    .sort_values(ascending=False)
    .tail(10)
)

And the results:

state
MAINE                   2.0
IDAHO                   2.0
HAWAII                  2.0
SOUTH DAKOTA            0.0
NORTH DAKOTA            0.0
ALASKA                  0.0
VERMONT                 0.0
DELAWARE                0.0
WYOMING                 0.0
DISTRICT OF COLUMBIA    NaN
Name: district, dtype: float64

Wondering how a state can have 0 representatives? That’s not what this means; rather, there is only a single Congressional district in each of these states. Thus, it doesn’t get a number, and the data set shows it to be 0. Even worse is Washington, DC, which has no Congressional representative at all. (I know, it seems weird to me, too.)

What states gained districts from 2020 to 2022? How many new seats did they get? What states lost districts from 2020 to 2022? How many seats did they lose?

Each state is allocated a number of Congressional representatives based on its proportion of the population as found by the census, taken once per decade. Since the last census was done in 2020, the number of seats allocated to each state changed between 2020 and 2022. The question is: By how much did each change, and which states netted the greatest gains and losses?

First, we’ll only need the rows from the years 2020 and 2022. We can do that by (again) setting the index to be the “year” column, and then using “fancy indexing” to retrieve rows with either 2020 or 2022 as the index:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
)

With that in place, what do we want? Well, we want to get the max district number (as before) per year and state. This means that we’ll once again do a “groupby”, but this time we’ll group by two separate columns, both “year” and “state”, choosing the max on district:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
    .groupby(['year', 'state'], observed=True)['district']
    .max()
)

The result is a series with a two-level index (year and state), whose values show the maximum value of the Congressional district for that state, in that year:

year  state        
2020  ALABAMA           7
      ALASKA            0
      ARIZONA           9
      ARKANSAS          4
      CALIFORNIA       53
                       ..
2022  VIRGINIA         11
      WASHINGTON       10
      WEST VIRGINIA     2
      WISCONSIN         8
      WYOMING           0
Name: district, Length: 101, dtype: int64

We’ll want to subtract the number of seats in 2022 from the number in 2020. This means that we’ll need to have each of the years in a separate column. We can, I think, most easily do this by turning the year (currently the outermost level in the series multi-index) into the columns of a data frame. We can do that with “unstack”:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
    .groupby(['year', 'state'], observed=True)['district']
    .max()
    .sort_values(ascending=False)
    .unstack(level=0)
)

By default, “unstack” will take the innermost part of a multi-index and turn it into the columns. But we want to use the outermost part of the multi-index, so we pass “level=0” as a keyword argument. The result is a data frame in which the index contains state names, and we have columns “2020” and “2022”.

That’s great, but how can we find out how many seats each got? We basically want to subtract the number in 2022 from the number in 2020. We can do that with the “diff” method, specifying that we want to subtract across columns (and not rows) by specifying the axis:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
    .groupby(['year', 'state'], observed=True)['district']
    .max()
    .sort_values(ascending=False)
    .unstack(level=0)
    .diff(axis='columns')
)

Great! We now have two columns in our data frame: The 2020 column contains NaN values (because it’s the base for our subtraction) and the 2022 column contains the difference.

Now what? Well, let’s sort the values that we have by the 2022 column, from largest to smallest:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
    .groupby(['year', 'state'], observed=True)['district']
    .max()
    .sort_values(ascending=False)
    .unstack(level=0)
    .diff(axis='columns')
    .sort_values(by=2022, ascending=False)
)

We could stop there, but let’s go a bit further, just to tidy things up. First of all, we can again use “loc” and a “lambda” to filter through the columns, only keeping those where the number in the 2022 column is greater than 0.

Then we can select the 2022 column, so that we only see that net:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
    .groupby(['year', 'state'], observed=True)['district']
    .max()
    .sort_values(ascending=False)
    .unstack(level=0)
    .diff(axis='columns')
    .sort_values(by=2022, ascending=False)
    .loc[lambda df_: df_[2022] > 0]
    [2022]
)

The result:

state
MONTANA           2.0
TEXAS             2.0
OREGON            1.0
COLORADO          1.0
FLORIDA           1.0
NORTH CAROLINA    1.0
Name: 2022, dtype: float64

We see that Montana and Texas gained two seats, and the others (Oregon, Colorado, Florida, and North Carolina) all gained one.

What about states that lost seats? We can turn around our “lambda”, looking for those rows that have a negative number:

(
    df
    .set_index('year')
    .loc[[2020, 2022]]
    .groupby(['year', 'state'], observed=True)['district']
    .max()
    .sort_values(ascending=False)
    .unstack(level=0)
    .diff(axis='columns')
    .sort_values(by=2022, ascending=False)
    .loc[lambda df_: df_[2022] < 0]
    [2022]
)

The results are:

state
OHIO            -1.0
ILLINOIS        -1.0
NEW YORK        -1.0
CALIFORNIA      -1.0
WEST VIRGINIA   -1.0
MICHIGAN        -1.0
PENNSYLVANIA    -1.0
Name: 2022, dtype: float64

Add a new column, `percentvotes`, for each race, indicating what percentage of the vote each candidate received.

Each row has a value for “candidatevotes”, indicating how many votes that candidate received. It also has “totalvotes”, showing how many votes were cast in that election. Here, I asked you to create a new column, “percentvotes”, so that we would know what percentage of the vote each candidate received.

Pandas makes this sort of calculation fairly straightforward: We can just divide the “candidatevotes” column by the “totalvotes” column. We will get back a new series, with the same index as the two other columns in our data frame. We can then assign this new series back to the data frame as a new column:

df['percentvotes'] = df['candidatevotes'] / df['totalvotes']

Create a table showing the winning candidates in each election in 2022, along with their state name, district number, and party.

With this in hand, can we find the winning candidate in each race? Absolutely! That’s because the winning candidate is the one who got more than 50 percent of the votes.

I’ll first set the index to be the “year” column, and then retrieve the year 2022. Then I’ll use “lambda” to find only those rows where the “percentvotes” column has a values > 0.5. Finally, I can use double square brackets to retrieve only four columns:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    [['state', 'district', 'candidate', 'party']]
)

Sure enough, I now get the winners:

With this in place, we can ask all sorts of additional questions. For example, how many representatives won with less than 51 percent of the vote?

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: (df_['percentvotes'] > 0.5) & 
                     (df_['percentvotes'] < 0.51) ]
    [['state', 'district', 'candidate', 'party']]
)

Turns out that there are 11 such squeaker races:

Calculate how many representatives from each party were elected in 2022. Which party is now in the majority?

Now that we have a list of the winners, let’s calculate the majority party in Congress. This will mean (mostly) repeating the above query, but only grabbing the “party” column:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    ['party']
)

Now that we have a series with the winning parties, we can use “value_counts” to tell us how many members there are of each party:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    ['party']
    .value_counts()
)

We get the following:

party
REPUBLICAN    213
DEMOCRAT      205
Name: count, dtype: int64

This tells us what we already knew, that the Republican party is in the majority, but only just barely. They get to choose the speaker… or oust him, as we saw this week!

Create a table showing, for each state, the party that won a majority of seats in Congress in 2022.

Finally: How can we find which party has a majority of seats for each state? I decided to do this by first finding the winner (i.e., whoever has “percentvotes”) > 50 percent). I then decided to run a “groupby” on the combination of state and party, counting how many times the state abbreviation appeared:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    .groupby(['state', 'party'], observed=True)['state_po']
    .count()
)

The result of this query was a series with a multi-index (state + party). The values in the series were the number of seats that each party got in that state:

state          party     
ALABAMA        DEMOCRAT      1
               REPUBLICAN    6
ARIZONA        DEMOCRAT      3
               REPUBLICAN    6
ARKANSAS       REPUBLICAN    4
                            ..
WASHINGTON     REPUBLICAN    2
WEST VIRGINIA  REPUBLICAN    2
WISCONSIN      DEMOCRAT      2
               REPUBLICAN    6
WYOMING        REPUBLICAN    1
Name: state_po, Length: 78, dtype: int64

This is great, but how can we then find out which party has more seats? First, I decided to move the “party” portion of the multi-index to be columns, and thus make a data frame, via “unstack”:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    .groupby(['state', 'party'], observed=True)['state_po']
    .count()
    .unstack()
)

Now I had the number of Democratic and Republican representatives from each state. I then decided to use “assign” to create three new columns:

I then retrieved only those columns:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    .groupby(['state', 'party'], observed=True)['state_po']
    .count()
    .unstack()
    .assign(maj_d=lambda df_: df_['DEMOCRAT'] > df_['REPUBLICAN'],
            maj_r=lambda df_: df_['DEMOCRAT'] < df_['REPUBLICAN'],
            equal=lambda df_: df_['DEMOCRAT'] == df_['REPUBLICAN'])
    [['maj_d', 'maj_r', 'equal']]
)

This allows me to see which states have a majority of each party or (in only two cases) a tie. We can even count how many states are in each category by invoking “sum” on these columns. Since True is 1 and False is 0, the result for each column will be the number of states with a majority of each party:

(
    df
    .set_index('year')
    .loc[2022]
    .loc[lambda df_: df_['percentvotes'] > 0.5]
    .groupby(['state', 'party'], observed=True)['state_po']
    .count()
    .unstack()
    .assign(maj_d=lambda df_: df_['DEMOCRAT'] > df_['REPUBLICAN'],
            maj_r=lambda df_: df_['DEMOCRAT'] < df_['REPUBLICAN'],
            equal=lambda df_: df_['DEMOCRAT'] == df_['REPUBLICAN'])
    [['maj_d', 'maj_r', 'equal']]
    .sum()
)
party
maj_d    12
maj_r    15
equal     2
dtype: int64

But wait a second: What if we just want to get a series back, in which the states are the index, and the values are the party names?

Well, we can start by setting the index to be the year, and retrieving the rows for 2022, as before.

But then we can assign a new column, “won”, to be True when the candidate’s vote count is greater than 50 percent:

(
    df
    .set_index('year')
    .loc[2022]
    .assign(won=lambda df_: df_['percentvotes'] > 0.5)
)

With that in place, we can now run a “groupby” on both “state” and “party”, keeping only observed values, and summing the number of True values in “won”:

(
    df
    .set_index('year')
    .loc[2022]
    .assign(won=lambda df_: df_['percentvotes'] > 0.5)
    .groupby(['state', 'party'], observed=True)['won']
    .sum()
)

That’ll return a series whose multi-index has state and party, and whose values are integers — the number of votes that each party got.

Then we run a second groupby (yes, a second groupby!), using “state”, and using “idxmax” to get the index of the highest value that it finds:

(
    df
    .set_index('year')
    .loc[2022]
    .assign(won=lambda df_: df_['percentvotes'] > 0.5)
    .groupby(['state', 'party'], observed=True)['won']
    .sum()
    .groupby('state')
    .idxmax()
    .str.get(1)
)

We get the following back:

state
ALABAMA                  (ALABAMA, REPUBLICAN)
ALASKA                      (ALASKA, DEMOCRAT)
ARIZONA                  (ARIZONA, REPUBLICAN)
ARKANSAS                (ARKANSAS, REPUBLICAN)
CALIFORNIA              (CALIFORNIA, DEMOCRAT)
COLORADO                  (COLORADO, DEMOCRAT)
CONNECTICUT            (CONNECTICUT, DEMOCRAT)
DELAWARE                  (DELAWARE, DEMOCRAT)
FLORIDA                  (FLORIDA, REPUBLICAN)
GEORGIA                  (GEORGIA, REPUBLICAN)
HAWAII                      (HAWAII, DEMOCRAT)
IDAHO                      (IDAHO, REPUBLICAN)
ILLINOIS                  (ILLINOIS, DEMOCRAT)
INDIANA                  (INDIANA, REPUBLICAN)
IOWA                        (IOWA, REPUBLICAN)
KANSAS                    (KANSAS, REPUBLICAN)
KENTUCKY                (KENTUCKY, REPUBLICAN)
LOUISIANA              (LOUISIANA, REPUBLICAN)
MAINE                        (MAINE, DEMOCRAT)
MARYLAND                  (MARYLAND, DEMOCRAT)
MASSACHUSETTS        (MASSACHUSETTS, DEMOCRAT)
MICHIGAN                  (MICHIGAN, DEMOCRAT)
MINNESOTA                (MINNESOTA, DEMOCRAT)
MISSISSIPPI          (MISSISSIPPI, REPUBLICAN)
MISSOURI                (MISSOURI, REPUBLICAN)
MONTANA                  (MONTANA, REPUBLICAN)
NEBRASKA                (NEBRASKA, REPUBLICAN)
NEVADA                      (NEVADA, DEMOCRAT)
NEW HAMPSHIRE        (NEW HAMPSHIRE, DEMOCRAT)
NEW JERSEY              (NEW JERSEY, DEMOCRAT)
NEW MEXICO              (NEW MEXICO, DEMOCRAT)
NEW YORK                  (NEW YORK, DEMOCRAT)
NORTH CAROLINA      (NORTH CAROLINA, DEMOCRAT)
NORTH DAKOTA        (NORTH DAKOTA, REPUBLICAN)
OHIO                        (OHIO, REPUBLICAN)
OKLAHOMA                (OKLAHOMA, REPUBLICAN)
OREGON                      (OREGON, DEMOCRAT)
PENNSYLVANIA          (PENNSYLVANIA, DEMOCRAT)
RHODE ISLAND          (RHODE ISLAND, DEMOCRAT)
SOUTH CAROLINA    (SOUTH CAROLINA, REPUBLICAN)
SOUTH DAKOTA        (SOUTH DAKOTA, REPUBLICAN)
TENNESSEE              (TENNESSEE, REPUBLICAN)
TEXAS                      (TEXAS, REPUBLICAN)
UTAH                        (UTAH, REPUBLICAN)
VERMONT                    (VERMONT, DEMOCRAT)
VIRGINIA                  (VIRGINIA, DEMOCRAT)
WASHINGTON              (WASHINGTON, DEMOCRAT)
WEST VIRGINIA      (WEST VIRGINIA, REPUBLICAN)
WISCONSIN              (WISCONSIN, REPUBLICAN)
WYOMING                  (WYOMING, REPUBLICAN)
Name: won, dtype: object

That’s right — it’s a series of tuples! We’re only interested in the second item in the tuple (i.e., index 1). The easiest way to deal with that is to use the “str” accessor, and its “get” method. And no, these aren’t strings, but yes, this will work, and is a cool trick:

(
    df
    .set_index('year')
    .loc[2022]
    .assign(won=lambda df_: df_['percentvotes'] > 0.5)
    .groupby(['state', 'party'], observed=True)['won']
    .sum()
    .groupby('state')
    .idxmax()
    .str.get(1)
)

Sure enough, we now get the index (i.e., the states) and the majority party for each state:

state
ALABAMA           REPUBLICAN
ALASKA              DEMOCRAT
ARIZONA           REPUBLICAN
ARKANSAS          REPUBLICAN
CALIFORNIA          DEMOCRAT
COLORADO            DEMOCRAT
CONNECTICUT         DEMOCRAT
DELAWARE            DEMOCRAT
FLORIDA           REPUBLICAN
GEORGIA           REPUBLICAN
HAWAII              DEMOCRAT
IDAHO             REPUBLICAN
ILLINOIS            DEMOCRAT
INDIANA           REPUBLICAN
IOWA              REPUBLICAN
KANSAS            REPUBLICAN
KENTUCKY          REPUBLICAN
LOUISIANA         REPUBLICAN
MAINE               DEMOCRAT
MARYLAND            DEMOCRAT
MASSACHUSETTS       DEMOCRAT
MICHIGAN            DEMOCRAT
MINNESOTA           DEMOCRAT
MISSISSIPPI       REPUBLICAN
MISSOURI          REPUBLICAN
MONTANA           REPUBLICAN
NEBRASKA          REPUBLICAN
NEVADA              DEMOCRAT
NEW HAMPSHIRE       DEMOCRAT
NEW JERSEY          DEMOCRAT
NEW MEXICO          DEMOCRAT
NEW YORK            DEMOCRAT
NORTH CAROLINA      DEMOCRAT
NORTH DAKOTA      REPUBLICAN
OHIO              REPUBLICAN
OKLAHOMA          REPUBLICAN
OREGON              DEMOCRAT
PENNSYLVANIA        DEMOCRAT
RHODE ISLAND        DEMOCRAT
SOUTH CAROLINA    REPUBLICAN
SOUTH DAKOTA      REPUBLICAN
TENNESSEE         REPUBLICAN
TEXAS             REPUBLICAN
UTAH              REPUBLICAN
VERMONT             DEMOCRAT
VIRGINIA            DEMOCRAT
WASHINGTON          DEMOCRAT
WEST VIRGINIA     REPUBLICAN
WISCONSIN         REPUBLICAN
WYOMING           REPUBLICAN
Name: won, dtype: object

While this might seem like a silly thing to keep track of, it’s actually crucial in US presidential races. That’s because if no candidate gets a majority in the electoral college, each state in the House gets one vote and can choose the president. That hasn’t ever happened before… but of course, we’re now seeing lots of things that haven’t happened before!

We can figure out how many states have a majority of each party with a call to our friend value_counts:

won
REPUBLICAN    26
DEMOCRAT      24
Name: count, dtype: int64

The Jupyter notebook I used is here: https://drive.google.com/file/d/1kMoVFZ4C1z2lk9OwIjaNfM5wL-kSRAsp/view?usp=sharing

Questions, comments, or thoughts? Share them with me in the comments!

I’ll be back on Wednesday with additional problems to solve with Pandas.

Reuven