My Brother, My Brother and Me and the McElroy Brand

The McElroy brothers are NOT experts, and their advice should NEVER be followed. Travis insists he's a sexpert, but if there's a degree on his wall I haven't seen it. Also: this show isn't for kids, which I mention only so the babies out there know how cool they are for listening. What's up you cool baby?

Each of the episodes of the My Brother, My Brother and Me podcast begins with the above intro by Bob Ball. MBMBaM is a weekly advice podcast for the modern era created by the McElroy brothers: oldest brother Justin, middlest brother Travis, and sweet baby brother and Forbes' Thirty under Thirty media luminary Griffin. The podcast was started by the brothers as a way for them to keep in touch, and has evolved over time to feature guest experts, or guesperts, and led to several spinoffs and solo projects for the brothers. Over time, listeners have followed the brothers through major life and career events, as the brothers became fathers, quit their office jobs to focus on the podcasting game, and embarked on the journey of creating a short-lived television show of the same name.
The dynamic of the show has changed noticeably from their first hundred episodes, noticeably in terms of tone and content, but I am interested in how the dynamic between the brothers and the basic layout of the podcast has changed as they've grown in popularity and expanded their projects.

Data Scraping

To begin, we need a complete list of episodes, which luckily have been collected and extensively detailed by fans of the show on the MBMBaM Wiki. The list of episodes records the episode number, title, and air date for the over 500 episodes currently out, as well as links to the episode pages which documents the descriptions and outlines of each episode, and links to full transcripts of some episodes.

In [1]:
from bs4 import BeautifulSoup
import requests
import pandas as pd
import numpy as np
from datetime import datetime as dt
import re
!pip install html5lib
Requirement already satisfied: html5lib in /opt/conda/lib/python3.8/site-packages (1.1)
Requirement already satisfied: webencodings in /opt/conda/lib/python3.8/site-packages (from html5lib) (0.5.1)
Requirement already satisfied: six>=1.9 in /opt/conda/lib/python3.8/site-packages (from html5lib) (1.15.0)
In [2]:
url = "https://mbmbam.fandom.com"
eps = "/wiki/Episodes"
headers = {"user-agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"}
request = requests.get(url+eps, headers=headers)

big_soup = BeautifulSoup(request.text, features="html.parser")

# the episodes are seperated into two tables,
# one for before and after the brothers joined
# the Maximum Fun collective
big_string = big_soup.find_all('table')
pre_era = big_string[0].prettify()
max_fun = big_string[1].prettify()

list_pre = pd.read_html(pre_era, flavor='html5lib')
list_max = pd.read_html(max_fun, flavor='html5lib')

df1 = pd.DataFrame(list_pre[0])
df2 = pd.DataFrame(list_max[0])

df = pd.concat([df1, df2], ignore_index=True)
df.columns = ["Ep_Num", "Title", "Date", "mp3", "Notes"]

scripts = []
live = []
for i,r in df.iterrows():
    if "Transcript" in str(r["Notes"]):
        scripts.append(True)
    else:
        scripts.append(False)
    if "Face 2 Face" in r["Title"]:
        live.append(True)
    else:
        live.append(False)
    
    # converting date to useable datetime format
    day = r["Date"].replace("🎃", "O")
    date = dt.strptime(day, "%B %d, %Y")
    df.at[i, "Date"] = date
    
    if r["Ep_Num"] == "Sp":
        r["Ep_Num"] = -1
    else:
        r["Ep_Num"] = int(r["Ep_Num"])

df["Script_Avail"] = scripts
df["Live"] = live

df.drop(['mp3', 'Notes'], inplace=True, axis=1)

Taking data from the Guestsperts page on the MBMBMaM Wiki, we can add information to the table about who has been featured as a guestspert in which episode.

In [3]:
url_guest = "/wiki/Guestsperts"
r_guest = requests.get(url+url_guest, headers=headers)
med_soup = BeautifulSoup(r_guest.text, features="html.parser")
guest_html = med_soup.find('table').prettify()

guest_list = pd.read_html(guest_html, flavor='html5lib')
guests = pd.DataFrame(guest_list[0])

for i,r in guests.iterrows():
    match = re.search(r"^Episode", r["Episodes"])
    if not match:
        guests.drop(i, inplace=True)
    else:
        guests.at[i, "Episodes"] = int(r["Episodes"].split()[1])

guestsperts = []

for i,r in df.iterrows():
    guest = guests[guests["Episodes"]==r["Ep_Num"]]["Guestspert"]
    if guest.any():
        guestsperts.append(guest.values[0])
    else:
        guestsperts.append(np.nan)

df["Guestspert"] = guestsperts

Visualization

With a fuller list of episodes and information, we can create a visual aid to show us how often the brothers post live shows, host special episodes, and invite guests onto the show. To add context, vertical lines indicate major events such as when the show started, when each brother had their first child, and their first time at a red carpet event.

In [4]:
import matplotlib.pyplot as plt

data = [[]]*3
data[0] = [1 if i else np.nan for i in df["Live"]]
data[1] = [2 if i < 0 else np.nan for i in df["Ep_Num"]]
data[2] = [3 if type(i) == str else np.nan for i in df["Guestspert"]]
x = df["Date"]

fig, ax = plt.subplots(figsize=(15,5))
plt.plot(x, data[0], 'o', color='red')
plt.plot(x, data[1], 'o', color='orange')
plt.plot(x, data[2], 'o', color='lightgreen')
plt.yticks(np.arange(4), ["", "Live shows", "Specials", "Guestsperts"])
plt.xlabel("Years")
plt.title("Special Episodes Over the Run of the Show")

# First Episode of the show
plt.axvline(dt.strptime("04-12-2010", "%m-%d-%Y"))
# Justin's first kid
plt.axvline(dt.strptime("08-12-2014", "%m-%d-%Y"))
# Travis' first kid
plt.axvline(dt.strptime("10-25-2016", "%m-%d-%Y"))
# Griffin's first kid
plt.axvline(dt.strptime("11-25-2016", "%m-%d-%Y"))
# Margaritaville red carpet
plt.axvline(df[df["Ep_Num"]==400]["Date"])

plt.show()

Based on the graph above there is a noticeable increase in the number of live shows posted after all three brothers became fathers around the end of 2016, which makes sense because they would have had less time to record and edit their typical weekly shows, and depended more on posting older, previously recorded live shows.
There is also an increase in the number of guestsperts invited to the show after their invitation to the red carpet event for Jimmy Buffett's Broadway debut of Escape to Margaritaville, which brought them in contact with Marilu Henner, who later featured as a guestspert in episode 414.

Episode Details and Transcripts

To get more details, I compiled a list of links to each episode page in order to access the episode descriptions. Any missing links were manually added. The episode descriptions and suggested talking points give us a better idea of the topics covered in each episode.

In [5]:
combined = pre_era + max_fun
links = []
script_links = [np.nan for i in range(len(df))]
count = -1

combined = [i for i in combined.split("<") if len(i) > 30]
for l in combined:
    if "href" in l:
        link = l.split("\"")[1].split("\"")[0]
        
        # obtain the correct episode page links
        to_add = " " not in link and "Transcript" not in link 
        to_add = to_add and "Rachel" not in link 
        to_add = to_add and "Teresa" not in link 
        to_add = to_add and "Travis_M" not in link
        
        if to_add:
            links.append(link)
            count += 1
            
    if "Full Transcript" in l:
        script = l.split("href=\"")[1].split("\"")[0]
        if "dropbox" not in script:
            script_links[count] = script

# going through links to see if any need to be manually entered
for i in range(len(links)):
    if "mw" in links[i]:
        print(str(i) + ": " + df["Title"][i])
158: Face 2 Face 6: MaxFunCon2013
218: MBMBaM: The Adventure Zone
252: Bro's Better, Bro's Best: Ch. 62- 71
In [6]:
links[158] = "/wiki/Episode_155:_Face_2_Face_6:_MaxFunCon_2013"
links[218] = "/wiki/Adventure_Zone"
links[252] = "/wiki/Episode_245:_Bro%27s_Better,_Bro%27s_Best:_Ch._62-_71"

Now that we have the links to each episode page, we can start adding episode descriptions. The Suggested Talking Points can give us a better idea of the topics covered in each episode, although they don't exist for live shows. The two extra-special episodes, the Adventure Zone pilot and the Big Gulp MaxFun donor special, are manually added since they don't fit the typical episode format.

In [7]:
descriptions = []
talk_pts = []

for i, r in df.iterrows():
    
    req = requests.get(url+links[i], headers=headers)
    small_soup = BeautifulSoup(req.text, features="html.parser")
    
    # 'The Adventure Zone' also functions as the pilot to their 
    # DnD spinoff podcast of the same name
    if "Adventure Zone" in r["Title"]:
        descriptions.append("Episode 1 of The Adventure Zone.")
        talk_pts.append(np.nan)
        continue
    
    # 'The Big Gulp' is a donor special, supposedly the 36th
    # episode of a different podcast that does not exist
    if "Big Gulp" in r["Title"]:
        big_gulp = "Episode 36 of Big Gulp, a fictional vore-themed "
        big_gulp += "podcast hosted by Griffin and Travis McElroy."
        descriptions.append(big_gulp)
        talk_pts.append(np.nan)
        continue
    
    split_soup = small_soup.text.replace("[edit | edit source]", "")
    split_soup = split_soup.split("Description")
    
    d_id = len(split_soup) - 1
    ep_desc = split_soup[d_id]
    ep_desc = ep_desc.split("\n")
    ep_desc = [i for i in ep_desc if i]
    
    descriptions.append(ep_desc[0])
    
    if "Suggested" in ep_desc[1]:
        if "Outline" in ep_desc[2]:
            talk_pts.append(ep_desc[1].split(": ")[1])
        else:
            talk_pts.append(ep_desc[2])
    else:
        talk_pts.append(np.nan)
        
df["Ep_Desc"] = descriptions
df["Sugg_Talking_Pts"] = talk_pts

For a more complete list of transcripts, official transcripts of the show can be found on the Maximum Fun website for episodes 384 to 528. Since some of the linked transcripts of the Wiki page are more difficult to process, we are using the official transcripts.

In [8]:
prev = "https://maximumfun.org/transcripts/my-brother-my-brother-and-me"
prev += "/transcript-mbmbam-528-the-war-with-grandpa-watch/"

while(len(prev) > 0):
    r_script = requests.get(prev, headers=headers)
    scroup = BeautifulSoup(r_script.text, features="html.parser")
    
    title = str(scroup.find("title")).split(">")[1].split("<")[0]
    match = re.search(r"\d\d\d", title)
    if match:
        ep_num = int(match[0])
    elif "Single Sleeved" in title:
        ep_num = 498
    else:
        ep_num = -10
    
    cold_soup = str(scroup)
    if ep_num > 0 and "download\" href" in cold_soup:
        index = df.index[df["Ep_Num"]==ep_num][0]
        script_link = cold_soup.split("download\" href")[1].split("\"")[1]
        if type(script_links[index]) == float:
            script_links[index] = script_link
    
    if ep_num == 384:
        break
    if "transcript-previous" in str(scroup):
        prev = str(scroup).split("\"transcript-previous\">")[1]
        prev = prev.split("\"")[1]

With the list of transcripts, we can go back through the dataframe to update transcript availability for the next portion.

In [27]:
for i,r in df.iterrows():
    if type(script_links[i]) == float:
        r["Script_Avail"] = False
    if r["Ep_Num"] == 397:
        r["Script_Avail"] = False

Ten Episode Analysis

Now that we have a complete list of episode descriptions and available transcripts, we can begin to analyze the contents of the episodes. Since there are so many episodes, we will be looking at a smaller sample of ten episodes. The sample was selected to have transcripts available so that we can analyze them as well.

In [18]:
sample = df[df["Script_Avail"]==True].sample(20)
sample = sample[sample["Sugg_Talking_Pts"].notnull()].sample(10)
sample = sample.sort_values(by="Date")
sample
Out[18]:
Ep_Num Title Date Script_Avail Live Guestspert Ep_Desc Sugg_Talking_Pts
10 11 The Lesbian Apocalypse 2010-06-28 00:00:00 True False NaN After last week's up-close-and-personal encoun... Heavily Zydeco inspired, Chief Yogurt Tester, ...
130 128 Y Tu Hermano Tambien 2012-11-12 00:00:00 True False NaN Sex and death are the two most powerful primal... Gift Registry, Today Show Slash Fic, Burnin' R...
256 249 Toyota Bigraft 2015-04-27 00:00:00 True False NaN Can any of us really be sure that we've ever a... Blartwatch 2, Silent Lawnmowers, Fishgetter X,...
273 265 The Ballad of Tit Liquid 2015-08-19 00:00:00 True False NaN We'll be the first ones to admit that the titl... Fiddler on the Ground, Three Badges Deep, Foot...
276 268 Hot Boiled Beans and Bacon 2015-09-07 00:00:00 True False NaN Take up your enchanted blade and wooden boxing... ANTM Talk, Time Travel Backflip, Snugglebug, P...
300 290 Kung Fu Panda 3 Watch 2016-02-16 00:00:00 True False NaN Welcome, all, to our most fanciful episode yet... The Hug Heard Round the World, Mushroom Movie ...
421 407 Morton Shart's Joke School 2018-05-21 00:00:00 True False NaN In this episode, Justin reveals to the rest of... A Georgia-Fried Prawnline, Saddle Bag Spanking...
473 458 Race Island: A Horse Show 2019-05-06 00:00:00 True False NaN A criminal crime happened in front of a breath... A Robbery on the Racetrack, Darth Navarro, Bat...
483 468 Down the Soda Hole 2019-07-15 00:00:00 True False Laura Kate Dale According to our editing software, this one is... Standing Energy, Joe vs. Unassigned Carbon, Je...
520 504 The Nasty Buns 2020-03-30 00:00:00 True False NaN In which we find a hero in these trying times:... Look to Britney, Porch-Stable Food, Angel Baby...
In [19]:
print("Suggested Talking Points")
for i, r in sample.iterrows():
    print("Episode " + str(r["Ep_Num"]) + ": " + r["Sugg_Talking_Pts"])
Suggested Talking Points
Episode 11: Heavily Zydeco inspired, Chief Yogurt Tester, Motorhead or other adult themes, derapitation, bevving out, accidental pedophile, two solid minutes of Austin Powers references, uggos.
Episode 128: Gift Registry, Today Show Slash Fic, Burnin' Rubber, Get Busy Child, Shitty Iron Man, Food Incentive, Down The Sexual Oubliette 
Episode 249: Blartwatch 2, Silent Lawnmowers, Fishgetter X, Montana Law, Cloud Peen, Cafe Arbando's, Dune Butt, House Reviews
Episode 265: Fiddler on the Ground, Three Badges Deep, Foot Kiss, Pretend Better, Multiverse Spoilers
Episode 268: ANTM Talk, Time Travel Backflip, Snugglebug, Probstgate, Boxing Funtime Squad, Food Delivery, 362 Hours, Cursed Sword
Episode 290: The Hug Heard Round the World, Mushroom Movie Editions, A Very Terrible Towel, Office Traps, Pirate Jeffcoats, Emu President
Episode 407: A Georgia-Fried Prawnline, Saddle Bag Spanking, Guy Club, Bathroom Friendship Window, The Worst Money Zone Transition in the History of Earth, The Sitting Tree, Einstein's Bones
Episode 458: A Robbery on the Racetrack, Darth Navarro, Batilda, Big Awesome Bones, Wet and International Hamburgers, Susan Office, A Thrifty Snip
Episode 468: Standing Energy, Joe vs. Unassigned Carbon, Jelly Bean Pouch, Secret Donuts, Unfireable (w/Guestpert Laura Kate Dale!), A Hospital for Humans and Birds
Episode 504: Look to Britney, Porch-Stable Food, Angel Babysitter, In My Lungs, Unfortunate Grocery Replacements, Burritoing with Gronk, Duck Interpreter

Very generally, we can see that the brothers moved quickly away from the more controversial or more polarizing topics of their early episodes ("accidental pedophile" in episode 11) to much more absurd topics ("Pirate Jeffcoats" in episode 290 and "Wet and International Hamburgers" in episode 458). Though the suggested talking points gives us an idea of the variety of topics covered in the show and its general sense of humor, it doesn't give us a very good idea of the general trends of the show. Instead, we can look at the transcripts of each episode to give us a general idea of how many times each brother speaks up in the episode and how that has changed over the run of the show after 10 years to see if and how their dynamic has changed.

In [28]:
import io
from PyPDF2 import PdfFileReader

grif = "Griffin:"
trav = "Travis:"
jus = "Justin:"

griff_speaks = [0]*10
trav_speaks = [0]*10
juice_speaks = [0]*10

ep_count = 0

for i,r in sample.iterrows():
    index = df.index[df["Ep_Num"]==r["Ep_Num"]][0]
    
    pdf_url = script_links[index]
    if "wiki" in pdf_url:
        pdf_url = url+pdf_url
    r = requests.get(pdf_url)
    
    if "fun.org" in pdf_url:
        f = io.BytesIO(r.content)
        reader = PdfFileReader(f)
        
        for i in range(reader.getNumPages()):
            contents = reader.getPage(i).extractText()
            contents = contents.replace("\n", "")
            
            griff_speaks[ep_count] += contents.count(grif)
            trav_speaks[ep_count] += contents.count(trav)
            juice_speaks[ep_count] += contents.count(jus)
    else:
        souped = BeautifulSoup(r.text, features="html.parser")
        contents = souped.text
        
        griff_speaks[ep_count] += contents.count(grif)
        trav_speaks[ep_count] += contents.count(trav)
        juice_speaks[ep_count] += contents.count(jus)
    
    ep_count += 1

With a count of the number of times each brother speaks in the ten sample episodes, we can use the three arrays to calculate the percentage of times each brother speaks up per episode, and create a smaller dataframe that we can then use to visualize the data.

In [26]:
total = []
grif_perc = []
trav_perc = []
jus_perc = []

grif = ["Griffin"]
trav = ["Travis"]
jus = ["Justin"]

for i in range(10):
    count = griff_speaks[i]+trav_speaks[i]+juice_speaks[i]
    
    total.append(count)
    grif_perc.append(griff_speaks[i]/count*100)
    trav_perc.append(trav_speaks[i]/count*100)
    jus_perc.append(juice_speaks[i]/count*100)

mini_data = {'Ep_Num': list(sample["Ep_Num"])*3, 
             'Date': list(sample["Date"])*3, 
             'Brother': grif*10 + trav*10 + jus*10,
             'Percent_Speaking': grif_perc+trav_perc+jus_perc}
mini_df = pd.DataFrame(data=mini_data)
mini_df
Out[26]:
Ep_Num Date Brother Percent_Speaking
0 11 2010-06-28 Griffin 33.980583
1 128 2012-11-12 Griffin 38.688946
2 249 2015-04-27 Griffin 34.210526
3 265 2015-08-19 Griffin 39.130435
4 268 2015-09-07 Griffin 35.448916
5 290 2016-02-16 Griffin 35.457706
6 407 2018-05-21 Griffin 32.737276
7 458 2019-05-06 Griffin 33.840948
8 468 2019-07-15 Griffin 33.946078
9 504 2020-03-30 Griffin 33.333333
10 11 2010-06-28 Travis 26.699029
11 128 2012-11-12 Travis 25.578406
12 249 2015-04-27 Travis 29.605263
13 265 2015-08-19 Travis 29.755435
14 268 2015-09-07 Travis 27.708978
15 290 2016-02-16 Travis 32.444959
16 407 2018-05-21 Travis 32.187070
17 458 2019-05-06 Travis 33.502538
18 468 2019-07-15 Travis 36.642157
19 504 2020-03-30 Travis 35.294118
20 11 2010-06-28 Justin 39.320388
21 128 2012-11-12 Justin 35.732648
22 249 2015-04-27 Justin 36.184211
23 265 2015-08-19 Justin 31.114130
24 268 2015-09-07 Justin 36.842105
25 290 2016-02-16 Justin 32.097335
26 407 2018-05-21 Justin 35.075653
27 458 2019-05-06 Justin 32.656514
28 468 2019-07-15 Justin 29.411765
29 504 2020-03-30 Justin 31.372549

Sample Visualizations

With the ten-episode sample compiled into a single dataframe, we can visualize the sample data in a violin plot to see which brother speaks the most on average, and who speaks the most consistently.

In [25]:
import seaborn as sns
fig, ax = plt.subplots(figsize=(15,10))

# seaborn violin plot
# life expectancy across the years
g = sns.violinplot(
    data=mini_df,
    x="Brother", y="Percent_Speaking",
)

plt.title("Percentage of Times Each Brother Speaks")

plt.show()

The violin plot of the percentages each brother speaks shows that Travis speaks the least on average, and while Griffin and Justin speak the same percentages on average, Griffin speaks the most with more consistency at about 35% to 40%, while Justin varies more between speaking more and speaking less, varying between about 30% to 40%.

In [24]:
grif_df = mini_df[mini_df["Brother"]=="Griffin"].sort_values(by="Date")
trav_df = mini_df[mini_df["Brother"]=="Travis"].sort_values(by="Date")
jus_df = mini_df[mini_df["Brother"]=="Justin"].sort_values(by="Date")

fig, ax = plt.subplots(figsize=(15,10))

plt.plot(grif_df["Date"], grif_df["Percent_Speaking"])
plt.plot(trav_df["Date"], trav_df["Percent_Speaking"])
plt.plot(jus_df["Date"], jus_df["Percent_Speaking"])
plt.ylim(20, 50)
plt.legend(["Griffin", "Travis", "Justin"])
plt.xlabel("Years")
plt.ylabel("Percentage(%)")
plt.title("Percentage Each Brother Speaks over 10 Years")

# First Episode of the show
plt.axvline(dt.strptime("04-12-2010", "%m-%d-%Y"))
# Justin's first kid
plt.axvline(dt.strptime("08-12-2014", "%m-%d-%Y"))
# Travis' first kid
plt.axvline(dt.strptime("10-25-2016", "%m-%d-%Y"))
# Griffin's first kid
plt.axvline(dt.strptime("11-25-2016", "%m-%d-%Y"))
# Margaritaville red carpet
plt.axvline(df[df["Ep_Num"]==400]["Date"])

plt.show()

The plot over time shows us that the amount of time each brother spends speaking has average out as the years have gone by. In the first episodes of the show, Travis speaks the least by far, with Justin and Griffin alternately speaking more. However, at around the time the brothers are all becoming fathers, around 2016 to 2017, the percentage each brother speaks becomes much closer, indicating they are each spending about an equal amount of time speaking. Since the sample is small, we can only look at the general trend of the episodes, but it shows us there is a pretty clear equalizing trend as they continue to create more episodes.

Conclusion

Although there are a few noticeable changes in My Brother, My Brother and Me's dynamic and show formula over the years, in general very little seems to have changed expecially in terms of their content and sense of humor. Although examining the topics covered in each episode might be of interest, at a surface level their content appears to have remained consistent, especially after joining Maximum Fun in their 38th episode in 2012, leaning harder towards the absurdist humor and away from the more controversial topics that were present in their first episodes. There also seems to be more consistent guestspert features as the podcast has grown in popularity and the brothers have had more exposure to, in their words, "famous people" (Ep. 400).

More noteable changes have come from their greater reliance on posting live shows in lieu of their weekly recorded shows, and a small change in their dynamic over the years with Travis slowly increasing the percentage of times he speaks in a show, leading to a more equal dynamic among the brothers. Notably, these changes all seem to occur close to the time each brother becomes a father. This especially makes sense in terms of their increase use of live shows, as their growing familial responsibilities and other ongoing projects, as well as a growing catalog of previously recorded live shows, make posting live shows a simpler and more appealing alternative.