2 Constructing our data sets
Data is the raw material for the rest of this book. Before we can explore, model, segment, price, score leads, evaluate promotions, or simulate operations, we need a working set of tables that look enough like sports business data to be useful.
This chapter introduces those tables. The data is fictional, but the structure is based on common business problems inside a professional sports organization: customer records, season schedules, ticket inventory, secondary-market sales, demographics, survey responses, and operational observations. The examples use the Nashville Game Hens, an imaginary professional baseball team, but the lessons apply across teams, venues, and other live-event businesses.
The data used throughout the book lives in the FOSBAAS package. Earlier versions of this chapter spent a lot of time showing the original data-generation code. That was useful while the package was being built, but it is less useful for readers now. The package has been reconstructed, and the book should treat it as the stable source of teaching data. When you need a table, call it directly from the package:
library(FOSBAAS)
customer_renewals <- FOSBAAS::customer_renewals
season_data <- FOSBAAS::season_dataIn this chapter we will cover:
- What kinds of data appear in sports business analytics
- How the book’s example data sets fit together
- How to inspect the data before using it in later chapters
- How to generate small alternate examples when that helps explain the workflow
The goal is not to make you memorize every column. The goal is to make the rest of the book easier to follow because you understand what each table represents.
2.1 Working With R Data
The examples in this book use R (R Core Team 2025). R is common in statistics, visualization, teaching, and applied analytics. Python, SQL, spreadsheets, and BI tools are also common in sports organizations. The tool matters less than the habit: keep the work reproducible, inspect the data before modeling it, and connect the analysis to a business decision.
Code is useful because it records what happened. A spreadsheet can be fast, but it is often difficult to audit after several manual changes. A script makes the sequence of decisions visible: load the data, filter rows, join tables, calculate fields, summarize results, and create charts. That does not make code automatically correct. It makes the work easier to check.
Most examples in later chapters follow a simple pattern:
- Load a data set from
FOSBAAS - Inspect its columns and row counts
- Transform it into the shape needed for the analysis
- Build a graph, summary, model, or simulation
- Interpret the result in business terms
You do not need to install every package used in the entire book from inside this chapter. The render script and the chapter code load packages as needed. If something is missing, install the package named in the error message and rerun the chunk.
2.2 The Example Data Model
A sports organization does not usually work from one perfect table. Data is spread across ticketing systems, CRM tools, survey platforms, finance files, operations systems, and third-party vendors. Even when a vendor provides clean exports, analysis usually requires joining and reshaping multiple sources.
The FOSBAAS data is simplified, but it preserves the main relationships:
-
customer_datagives customers an identifier and a name. -
demographic_dataadds customer-level demographic and geographic attributes. -
season_datadescribes games, opponents, dates, promotions, and simulated demand. -
manifest_datadescribes seat inventory and price levels. -
secondary_dataconnects customers, seats, games, ticket types, and resale prices. -
customer_renewalsdescribes season-ticket accounts and renewal behavior. -
perceptual_dataandfa_survey_datasupport survey and segmentation examples. -
scan_dataandwait_times_datasupport operations examples.
These tables are cleaner than real operational data. That is intentional. The book needs data that is realistic enough to teach useful patterns but stable enough to run on a reader’s machine. Real data would include duplicate customers, missing values, inconsistent names, odd date formats, refunded transactions, seat relocations, package changes, and many other complications. We will still discuss those issues, but the teaching data keeps the focus on the analytical method.
You can see the available data objects exported by the package with:
ls("package:FOSBAAS")2.3 Customer Renewal Data
The customer_renewals table is used later to demonstrate lead scoring and renewal modeling. Each row represents a season-ticket account for a given season.
| variable | class | first_values |
|---|---|---|
| accountID | character | WD6TDY7C151R, X3SB8ADEML22 |
| corporate | character | i, c |
| season | numeric | 2021, 2021 |
| planType | character | p, f |
| ticketUsage | numeric | 0.728026975947432, 0.992104738159105 |
| tenure | numeric | 2, 19 |
| spend | numeric | 4908, 16410 |
| tickets | numeric | 6, 2 |
| distance | numeric | 61.6614648674555, 19.5341155295423 |
| renewed | character | nr, nr |
The most important fields are:
-
accountID: account identifier -
corporate: whether the account is corporate or individual -
season: season year -
planType: full or partial plan -
ticketUsage: share of tickets used -
tenure: years with the organization -
spend: ticket spend -
tickets: number of tickets on the account -
distance: distance from the ballpark -
renewed: renewal outcome
This is a customer-retention table. A team might use similar data to understand which accounts are at risk, which service behaviors matter, or which renewal offers should be prioritized. The exact columns vary by organization, but the pattern is common: account attributes, behavior, value, geography, and an outcome.
The package also includes the function used to create synthetic renewal-style data. You do not need to use this function for the rest of the book, but it is helpful for understanding how reproducible simulated data can be generated.
small_renewal_sample <- FOSBAAS::f_create_lead_scoring_data(
seed = 434,
num_purchasers = 100,
season = "2023",
f_tenure = FOSBAAS::f_calculate_tenure,
f_spend = FOSBAAS::f_calculate_spend,
f_use = FOSBAAS::f_calculate_ticket_use,
f_renewal_assignment = FOSBAAS::f_renewal_assignment,
f_assign_renewal = FOSBAAS::f_assign_renewal,
renew = FALSE
)
describe_data(small_renewal_sample, "Small generated renewal sample")| variable | class | first_values |
|---|---|---|
| accountID | character | IQ7G9BPWYJSD, L3TA5OEHCAAF |
| corporate | character | i, i |
| season | character | 2023, 2023 |
| planType | character | f, f |
| ticketUsage | numeric | 0.726619377103634, 0.880502448994666 |
| tenure | numeric | 0, 0 |
| spend | numeric | 8848, 4892 |
| tickets | numeric | 4, 2 |
| distance | numeric | 25.237493169165, 4.39980797935277 |
The important idea is not the specific simulation formula. The important idea is reproducibility. The same seed and arguments produce the same synthetic data, which means the examples in the book can be checked, rerun, and modified.
2.4 Season Data
The season_data table is a simulated schedule and demand table. Professional baseball teams usually play 81 regular-season home games. This package contains three simulated seasons, for 243 rows.
| variable | class | first_values |
|---|---|---|
| gameNumber | numeric | 1, 2 |
| team | character | SF, SF |
| date | Date | 2022-03-27, 2022-03-28 |
| dayOfWeek | character | Sun, Mon |
| month | character | Mar, Mar |
| weekEnd | logical | FALSE, FALSE |
| schoolInOut | logical | FALSE, FALSE |
| daysSinceLastGame | numeric | 50, 1 |
| openingDay | logical | TRUE, FALSE |
| promotion | character | none, none |
| ticketSales | numeric | 42928, 25759 |
| season | numeric | 2022, 2022 |
The table includes game number, opponent, date, day of week, month, weekend flag, school-in-session flag, rest between games, opening day, promotion type, ticket sales, and season.
This table supports forecasting and pricing examples later in the book. The simulated sales values are intentionally patterned. Weekend games, summer games, certain opponents, opening day, and promotions can all affect demand. In real work, you might add more variables:
- Team record and opponent quality
- Playoff probability
- Weather
- Giveaway type and media support
- Start time
- Dynamic price changes
- Competing local events
The simplified table is still useful because it lets us practice the core workflow: define demand drivers, visualize sales, estimate relationships, and interpret the result.
2.5 Manifest Data
A ticket manifest describes seat inventory. It is the practical upper bound of what can be sold, and it connects a physical seat to a section, row, seat number, and price.
| variable | class | first_values |
|---|---|---|
| seatID | numeric | 1, 2 |
| section | numeric | 1, 1 |
| sectionNumber | numeric | 1, 1 |
| rowNumber | numeric | 1, 1 |
| seatNumber | numeric | 1, 2 |
| seasonPrice | numeric | 190, 190 |
| groupPrice | numeric | 199.5, 199.5 |
| singlePrice | numeric | 218.5, 218.5 |
Manifest data matters because many ticketing questions are really inventory questions. A club may want to know which sections are underpriced, where unsold seats are concentrated, whether group prices are too close to single-game prices, or how season-ticket inventory should be protected.
Real manifests can be more complicated than this example. They may include obstructed views, accessible seating, premium products, holds, kills, suite inventory, variable price codes, and event-specific configuration changes.
2.6 Customer and Demographic Data
The customer_data table is intentionally minimal. It gives us customer identifiers and names.
| variable | class | first_values |
|---|---|---|
| custID | character | XVRB8F124HVE, 14F3TMMBK97A |
| name | character | Alice Stone, Jennifer Guerrero |
The demographic_data table extends the customer concept with fields often purchased from a third-party vendor or appended from a CRM enrichment process.
| variable | class | first_values |
|---|---|---|
| custID | character | MBT9G0X70NTI, QTR3JJJ5J6GJ |
| nameF | character | Philip, Evelyn |
| nameL | character | Riddle, Campos |
| nameFull | character | Philip Riddle, Evelyn Campos |
| gender | character | m, f |
| age | numeric | 55, 30 |
| latitude | numeric | 36.0491208455478, 37.1248221175549 |
| longitude | numeric | -86.0860894506936, -86.0744325353366 |
| distance | numeric | 48.34, 77.09 |
| maritalStatus | character | m, s |
| ethnicity | character | w, w |
| children | character | y, n |
| hhIncome | numeric | 2866, 552 |
| county | character | tennessee,wilson, kentucky,edmonson |
Demographic and geographic data can help with segmentation, market analysis, direct marketing, and sales prioritization. It should also be used carefully. Vendor-modeled fields can be wrong, stale, incomplete, or inappropriate for certain decisions. Before using demographic data, ask what decision it will support and whether the field is reliable enough for that purpose.
The example includes customer identifiers, names, gender, age, location, distance from the ballpark, marital status, ethnicity, children in household, household income, and county.
2.7 Secondary-Market Data
The secondary_data table represents ticket purchases or resale activity at the seat-game level.
| variable | class | first_values |
|---|---|---|
| seatID | numeric | 9010, 20950 |
| custID | character | N22J8UPWACNO, II3IGIN0PY15 |
| ticketType | character | se, se |
| gameID | numeric | 130, 111 |
| tickets | numeric | 1, 1 |
| priceKey | character | 9010_se, 20950_se |
| price | numeric | 54, 30 |
| orderedCluster | numeric | 8, 7 |
| secondayrPrice | numeric | 60.51, 37.11 |
This table connects several concepts:
-
seatIDlinks to the manifest. -
custIDlinks to a customer. -
ticketTypedistinguishes ticket categories. -
gameIDlinks to a game. -
ticketsrecords quantity. -
priceKey,price, andsecondayrPricedescribe price fields used in later examples.
The column name secondayrPrice is misspelled in the package data and is left as-is here so the book matches the current API. In production work, you would usually correct names during a data-cleaning step and keep a documented mapping back to the source system.
Secondary-market data is useful because it reveals demand signals that primary sales alone may miss. It can also be noisy. Prices may reflect fees, listing behavior, timing, seat quality, opponent demand, or brokers’ strategies. Treat it as a signal, not a perfect measure of willingness to pay.
2.8 Survey Data
Survey data appears in two forms in the package.
The first is perceptual_data, an aggregated table used for perceptual mapping. Each row represents a team or property, and each column counts how often respondents selected an attribute.
| Friendly | Exciting | Fresh | Inovative | Fun | Old | Historic | Winners | Great | Expensive |
|---|---|---|---|---|---|---|---|---|---|
| 1930 | 3080 | 1955 | 2128 | 2861 | 2757 | 280 | 1039 | 1781 | 3616 |
| 2646 | 4732 | 1444 | 2569 | 3508 | 4492 | 1775 | 3883 | 3422 | 3675 |
| 2928 | 3783 | 2959 | 3831 | 3417 | 3034 | 3034 | 1945 | 2876 | 2774 |
A question behind this table might ask:
How do you feel about the following sports properties? Please check all that apply for each team listed.
The second is fa_survey_data, a respondent-level survey table used for factor analysis and segmentation.
| variable | class | first_values |
|---|---|---|
| ReasonForAttending | character | Special Game, Support the Team |
| Socialize | numeric | 2, 4 |
| Tailgate | numeric | 3, 1 |
| TakeSelfies | numeric | 4, 4 |
| PostToSocialMedia | numeric | 2, 0 |
| SeeFriends | numeric | 2, 3 |
| VisitKidAttractions | numeric | 1, 1 |
| MeetMascot | numeric | 3, 2 |
| SnacksForKids | numeric | 3, 3 |
| KidsRunBases | numeric | 9, 2 |
| KidsSlide | numeric | 1, 2 |
| GetDinner | numeric | 4, 8 |
| EatParkFood | numeric | 1, 4 |
| SampleFood | numeric | 2, 2 |
| GetDrinks | numeric | 4, 5 |
| DrinkBeer | numeric | 4, 4 |
| BuyGear | numeric | 5, 0 |
| TourThePark | numeric | 1, 8 |
| VisitAttractions | numeric | 0, 2 |
| WatchPregameShow | numeric | 9, 3 |
| SeeTheChicken | numeric | 2, 0 |
| UpgradeSeats | numeric | 2, 1 |
| GetAutographs | numeric | 2, 1 |
| WatchGame | numeric | 9, 0 |
| SeePractice | numeric | 2, 1 |
| MeetPlayers | numeric | 1, 1 |
Survey data is different from behavioral data. Ticket purchases tell us what someone did. Survey responses tell us what someone says, remembers, believes, or prefers. Both are useful, and both can mislead. Good research design matters because bad sampling or poorly worded questions can produce confident answers to the wrong problem.
2.9 Pricing Survey Data
Later chapters use pricing research concepts such as the Van Westendorp price sensitivity meter. The package does not need a separate object for every small teaching example, so the following chunk creates a compact pricing survey data set directly in the chapter.
set.seed(715)
vw_data <- data.frame(
product = "DugoutSeats",
expected_price = round(rnorm(1000, 100, 10), 0),
too_expensive = round(rnorm(1000, 130, 20), 0),
too_cheap = round(rnorm(1000, 60, 15), 0),
bargain = round(rnorm(1000, 50, 10), 0),
too_cheap_to_trust = round(rnorm(1000, 160, 20), 0)
)
knitr::kable(
head(vw_data),
caption = "Van Westendorp-style survey data",
align = "c",
format = "markdown",
padding = 0
)| product | expected_price | too_expensive | too_cheap | bargain | too_cheap_to_trust |
|---|---|---|---|---|---|
| DugoutSeats | 100 | 115 | 57 | 39 | 170 |
| DugoutSeats | 113 | 147 | 69 | 52 | 129 |
| DugoutSeats | 102 | 169 | 67 | 51 | 163 |
| DugoutSeats | 103 | 134 | 72 | 54 | 148 |
| DugoutSeats | 99 | 88 | 46 | 37 | 163 |
| DugoutSeats | 83 | 123 | 77 | 59 | 145 |
A Van Westendorp-style pricing study asks respondents to identify price points that feel too expensive, too cheap, expensive but still possible, and a bargain. The exact wording matters, and pricing research should be interpreted alongside sales history, inventory constraints, brand positioning, and business goals.
2.10 Operations Data
Operations data helps teams understand how the venue works on event day. In this book, the operations examples focus on ingress and service times.
The scan_data table records simulated ticket scans by time interval.
| variable | class | first_values |
|---|---|---|
| observations | numeric | 1, 2 |
| scans | numeric | 0, 2 |
| action_time | hms, difftime | 61200, 61260 |
| date | character | 4/1/2024, 4/1/2024 |
The wait_times_data table records simulated service-process times.
| variable | class | first_values |
|---|---|---|
| transaction | numeric | 1, 2 |
| orderTimes | numeric | 39, 0 |
| paymentTimes | numeric | 28, 56 |
| fulfillTimes | numeric | 0, 7 |
| totalTime | numeric | 67, 63 |
Operational data often behaves differently from customer or sales data. It is time-dependent, process-dependent, and sensitive to bottlenecks. A small staffing change, gate closure, POS outage, weather event, or delayed crowd arrival can change the shape of the data quickly.
The package also exposes helper functions for creating scan curves:
small_scan_sample <- FOSBAAS::f_get_scan_data(
x_value = 150,
y_value = 500,
seed = 42,
sd_mod = 8
)
describe_data(small_scan_sample, "Small generated scan sample")| variable | class | first_values |
|---|---|---|
| observations | integer | 1, 2 |
| scans | numeric | 0, 8 |
This kind of simulated data is useful when you want to test an operational idea without exposing real venue data or waiting for a full season of observations.
2.11 Package Data and Reproducibility
The FOSBAAS package gives the book a stable data layer. That matters for three reasons.
First, it keeps the examples reproducible. Readers can run the same code and get the same tables.
Second, it keeps the chapters focused. A chapter on pricing should not need to rebuild a customer table before it can estimate demand.
Third, it makes the code easier to maintain. If the package data changes, the book should update the examples that use the public package objects and functions rather than copying large blocks of generation code into each chapter.
When working with package data, prefer explicit namespace calls:
FOSBAAS::season_data
FOSBAAS::customer_renewals
FOSBAAS::f_get_scan_dataThis makes it clear where the object came from and reduces confusion when multiple packages define functions with similar names.
2.12 Key Concepts and Chapter Summary
This chapter introduced the data used throughout the book. The tables are fictional, but they represent common categories of sports business data: customers, demographics, schedules, inventory, transactions, survey responses, pricing research, and operations.
The most important lessons are practical:
- Sports business data is relational. Useful analysis often requires connecting customers, games, seats, prices, and behavior.
- Clean teaching data is not the same as clean production data. Real data needs validation, cleaning, and documentation.
- Reproducible package data lets the book focus on analytical thinking instead of rebuilding source tables in every chapter.
- Simulated data is useful when it preserves the structure of a real problem and makes assumptions visible.
In Chapter 3, we will begin exploring these data sets with summaries and graphics. That exploration step is essential. Before you model, forecast, segment, or recommend a strategy, you need to understand what the data is actually saying.