MLS National TV Lineup

By Christian McDonald

Major League Soccer announced their 2018 national TV lineup on Thursday, Jan. 4th. A quick scan of the lineup seemed heavy on Atlanta, Seattle, Portland and the L.A. and N.Y. teams, but it was hard to know for sure. I didn't see Houston or Dallas very often, nor our maybe-Texas-team-to-be Columbus Crew.

So I thought I would practice some analysis skills and take a look at the matchups, seeing who was favored in the schedule. Surely TV market size has a lot to do with these results, but we needed another reason to talk about soccer in the office.

Goals

  • games by team
  • breakdown by language
  • Who is a home more

To Do

  • This is not DRY. I need to turn the chart/table making into a function, which I could then feed the language table into it.
  • I sure would like to add a title to the charts. Perhaps leather is too limited, but it does show how to do it in the leather docs, but it doesn't work the same inside agate.

Of note

What I see ...

Atlanta is clearly the darling overall, and why not after hosting the most-attended MLS game ever last year in the new Mercedes Benz stadium.

Atlanta is also a decent TV market, ranking 10th for 2016-2017, the latest rankings I could find.

MLS is clearly taking advantage of the innaugural season of LAFC to showcase the left-coast big-city rivalry, along with three showings of the New York derby. The popular Seattle and Portland teams are up there with still-relatively-new teams NYC FC and Orlando.

Austin's maybe-team-to-be gets only two national matches, and the other Texas teams don't fare too well on the English-language channels. That said, FC Dallas and the Houston Dynamo do top the Spanish matchups.

Atlanta also hosts the most games, with the LA teams and English-language channel Portland games also high on the list.

See the output below:

In [1]:
import agate
import warnings
warnings.filterwarnings('ignore')
In [2]:
raw = agate.Table.from_csv('../data/mlstv.csv')
print(raw)
| column    | data_type |
| --------- | --------- |
| Date Time | DateTime  |
| Date      | Date      |
| Match     | Text      |
| Network   | Text      |
| Spanish   | Boolean   |
| Home      | Text      |
| Visitor   | Text      |

In [3]:
# filtering for language

both = raw
english = raw.where(lambda r: r['Spanish'] is False)
spanish = raw.where(lambda r: r['Spanish'] is True)
print('len of both: {}'.format(len(both)))
print('len of english: {}'.format(len(english)))
print('len of spanish: {}'.format(len(spanish)))
len of both: 86
len of english: 61
len of spanish: 25

All National TV

In [13]:
# create a single list of all teams in matchups
home = both.select(['Home', 'Date']).rename(column_names = ['team', 'date'])
visitor = both.select(['Visitor', 'Date']).rename(column_names = ['team', 'date'])
teams = agate.Table.merge([visitor, home])

print('len of visitor: {}'.format(len(visitor)))
print('len of home: {}'.format(len(home)))
print('len of teams: {}\n'.format(len(teams)))
teams_pivot = teams.pivot('team').rename(column_names = {'Count': 'Matches'})
teams_chart = teams_pivot.order_by('team').order_by('Matches', reverse=True)
teams_chart.print_table(max_rows=None, max_column_width=None)
teams_chart.to_csv('../data/all_tv.csv')
teams_chart.bar_chart('team', 'Matches','../charts/all_tv.svg')
teams_chart.bar_chart('team', 'Matches')
len of visitor: 86
len of home: 86
len of teams: 172

| team                   | Matches |
| ---------------------- | ------- |
| Atlanta United         |      16 |
| LAFC                   |      16 |
| New York City FC       |      14 |
| New York Red Bulls     |      14 |
| LA Galaxy              |      13 |
| Orlando City SC        |      13 |
| Seattle Sounders       |      13 |
| D.C. United            |      12 |
| Portland Timbers       |      12 |
| Chicago Fire           |       7 |
| FC Dallas              |       7 |
| Sporting Kansas City   |       7 |
| Houston Dynamo         |       6 |
| San Jose Earthquakes   |       5 |
| Toronto FC             |       4 |
| Philadelphia Union     |       3 |
| Colorado Rapids        |       2 |
| Columbus Crew SC       |       2 |
| Minnesota United FC    |       2 |
| New England Revolution |       2 |
| Real Salt Lake         |       2 |
Out[13]:
Matches51015200teamReal Salt LakeNew England RevolutionMinnesota United FCColumbus Crew SCColorado RapidsPhiladelphia UnionToronto FCSan Jose EarthquakesHouston DynamoSporting Kansas CityFC DallasChicago FirePortland TimbersD.C. UnitedSeattle SoundersOrlando City SCLA GalaxyNew York Red BullsNew York City FCLAFCAtlanta United

English language

In [14]:
en_visitor = english.select(['Visitor', 'Date']).rename(column_names = ['team', 'date'])
en_home = english.select(['Home', 'Date']).rename(column_names = ['team', 'date'])
en_teams = agate.Table.merge([en_visitor, en_home])

print('len of en_visitor: {}'.format(len(en_visitor)))
print('len of en_home: {}'.format(len(en_home)))
print('len of en_teams: {}\n'.format(len(en_teams)))

en_teams_pivot = en_teams.pivot('team').rename(column_names = {'Count': 'Matches'})
en_teams_chart = en_teams_pivot.order_by('team').order_by('Matches', reverse=True)
en_teams_chart.to_csv('../data/en_tv.csv')
en_teams_chart.print_table(max_rows=None, max_column_width=None)
en_teams_chart.bar_chart('team', 'Matches','../charts/en_tv.svg')
en_teams_chart.bar_chart('team', 'Matches')
len of en_visitor: 61
len of en_home: 61
len of en_teams: 122

| team                   | Matches |
| ---------------------- | ------- |
| Seattle Sounders       |      13 |
| Atlanta United         |      12 |
| LAFC                   |      12 |
| New York City FC       |      11 |
| Orlando City SC        |      11 |
| Portland Timbers       |      11 |
| New York Red Bulls     |      10 |
| LA Galaxy              |       9 |
| D.C. United            |       8 |
| Sporting Kansas City   |       7 |
| Toronto FC             |       4 |
| Chicago Fire           |       2 |
| Columbus Crew SC       |       2 |
| Minnesota United FC    |       2 |
| Philadelphia Union     |       2 |
| Colorado Rapids        |       1 |
| FC Dallas              |       1 |
| Houston Dynamo         |       1 |
| New England Revolution |       1 |
| Real Salt Lake         |       1 |
| San Jose Earthquakes   |       1 |
Out[14]:
Matches510150teamSan Jose EarthquakesReal Salt LakeNew England RevolutionHouston DynamoFC DallasColorado RapidsPhiladelphia UnionMinnesota United FCColumbus Crew SCChicago FireToronto FCSporting Kansas CityD.C. UnitedLA GalaxyNew York Red BullsPortland TimbersOrlando City SCNew York City FCLAFCAtlanta UnitedSeattle Sounders

Spanish language

In [15]:
es_visitor = spanish.select(['Visitor', 'Date']).rename(column_names = ['team', 'date'])
es_home = spanish.select(['Home', 'Date']).rename(column_names = ['team', 'date'])
es_teams = agate.Table.merge([es_visitor, es_home])

print('len of es_visitor: {}'.format(len(es_visitor)))
print('len of es_home: {}'.format(len(es_home)))
print('len of es_teams: {}\n'.format(len(es_teams)))

es_teams_pivot = es_teams.pivot('team').rename(column_names = {'Count': 'Matches'})
es_teams_chart = es_teams_pivot.order_by('team').order_by('Matches', reverse=True)
es_teams_chart.to_csv('../data/es_tv.csv')
es_teams_chart.print_table(max_rows=None, max_column_width=None)
es_teams_chart.bar_chart('team', 'Matches','../charts/es_tv.svg')
es_teams_chart.bar_chart('team', 'Matches')
len of es_visitor: 25
len of es_home: 25
len of es_teams: 50

| team                   | Matches |
| ---------------------- | ------- |
| FC Dallas              |       6 |
| Chicago Fire           |       5 |
| Houston Dynamo         |       5 |
| Atlanta United         |       4 |
| D.C. United            |       4 |
| LA Galaxy              |       4 |
| LAFC                   |       4 |
| New York Red Bulls     |       4 |
| San Jose Earthquakes   |       4 |
| New York City FC       |       3 |
| Orlando City SC        |       2 |
| Colorado Rapids        |       1 |
| New England Revolution |       1 |
| Philadelphia Union     |       1 |
| Portland Timbers       |       1 |
| Real Salt Lake         |       1 |
Out[15]:
Matches2460teamReal Salt LakePortland TimbersPhiladelphia UnionNew England RevolutionColorado RapidsOrlando City SCNew York City FCSan Jose EarthquakesNew York Red BullsLAFCLA GalaxyD.C. UnitedAtlanta UnitedHouston DynamoChicago FireFC Dallas
In [7]:
la_derby = both.where(lambda row: row['Match'] in {
        'LA Galaxy vs. LAFC',
        'LAFC vs. LA Galaxy'}).order_by('Date')
la_derby.select(['Date', 'Match', 'Network', 'Spanish']).print_table(max_column_width=None)
|       Date | Match              | Network | Spanish |
| ---------- | ------------------ | ------- | ------- |
| 2018-03-31 | LA Galaxy vs. LAFC | FOX     |   False |
| 2018-07-26 | LAFC vs. LA Galaxy | ESPN    |   False |
| 2018-08-24 | LA Galaxy vs. LAFC | ESPN    |   False |
In [8]:
ny_derby = both.where(lambda row: row['Match'] in {
        'New York City FC vs. New York Red Bulls',
        'New York Red Bulls vs. New York City FC'}).order_by('Date')
ny_derby.select(['Date', 'Match', 'Network', 'Spanish']).print_table(max_column_width=None)
|       Date | Match                                   | Network | Spanish |
| ---------- | --------------------------------------- | ------- | ------- |
| 2018-05-05 | New York Red Bulls vs. New York City FC | ESPN    |   False |
| 2018-07-08 | New York City FC vs. New York Red Bulls | FS1     |   False |
| 2018-08-22 | New York City FC vs. New York Red Bulls | FS1     |   False |

Who hosts the most?

Who hosts the most home games on National TV?

In [9]:
home_team_pivot = both.pivot('Home').rename(column_names = {'Count': 'Matches'}).order_by(
    'Matches',
    reverse=True
)
home_team_pivot.print_table()
| Home                 | Matches |
| -------------------- | ------- |
| Atlanta United       |      10 |
| LAFC                 |       8 |
| LA Galaxy            |       7 |
| New York Red Bulls   |       7 |
| Portland Timbers     |       6 |
| Seattle Sounders     |       6 |
| D.C. United          |       6 |
| Sporting Kansas City |       5 |
| Orlando City SC      |       5 |
| Chicago Fire         |       5 |
| New York City FC     |       4 |
| Toronto FC           |       3 |
| Philadelphia Union   |       3 |
| Houston Dynamo       |       3 |
| San Jose Earthquakes |       2 |
| Colorado Rapids      |       2 |
| FC Dallas            |       2 |
| Columbus Crew SC     |       1 |
| Real Salt Lake       |       1 |
In [10]:
# Hosting on English national TV
en_home_team_pivot = english.pivot('Home').rename(column_names = {'Count': 'Matches'}).order_by(
    'Matches',
    reverse=True
)
en_home_team_pivot.print_table()
| Home                 | Matches |
| -------------------- | ------- |
| Atlanta United       |       8 |
| LA Galaxy            |       6 |
| Portland Timbers     |       6 |
| LAFC                 |       6 |
| Seattle Sounders     |       6 |
| Sporting Kansas City |       5 |
| D.C. United          |       5 |
| Orlando City SC      |       4 |
| New York Red Bulls   |       4 |
| Toronto FC           |       3 |
| New York City FC     |       2 |
| Philadelphia Union   |       2 |
| Chicago Fire         |       1 |
| San Jose Earthquakes |       1 |
| Colorado Rapids      |       1 |
| Columbus Crew SC     |       1 |
In [11]:
# Hosting on English national TV
es_home_team_pivot = spanish.pivot('Home').rename(column_names = {'Count': 'Matches'}).order_by(
    'Matches',
    reverse=True
)
es_home_team_pivot.print_table()
| Home                 | Matches |
| -------------------- | ------- |
| Chicago Fire         |       4 |
| Houston Dynamo       |       3 |
| New York Red Bulls   |       3 |
| New York City FC     |       2 |
| FC Dallas            |       2 |
| LAFC                 |       2 |
| Atlanta United       |       2 |
| Real Salt Lake       |       1 |
| Orlando City SC      |       1 |
| Philadelphia Union   |       1 |
| LA Galaxy            |       1 |
| San Jose Earthquakes |       1 |
| Colorado Rapids      |       1 |
| D.C. United          |       1 |

Does the same match appear on more than one network?

I pivot by both Date and Match, which will count records that have the both. I order in reverse to show there are no cases where two matchups appear more than one. I did test this by creating a fake dupe record to make sure it works.

In [12]:
dupe_check = both.pivot(['Date', 'Match'])
dupe_check.order_by('Count', reverse=True).limit(5).print_table(max_column_width=None)
|       Date | Match                                     | Count |
| ---------- | ----------------------------------------- | ----- |
| 2018-03-04 | Sporting Kansas City vs. New York City FC |     1 |
| 2018-03-04 | LA Galaxy vs. Portland Timbers            |     1 |
| 2018-03-04 | Seattle Sounders vs. LAFC                 |     1 |
| 2018-03-31 | LA Galaxy vs. LAFC                        |     1 |
| 2018-03-31 | San Jose Earthquakes vs. New York City FC |     1 |