Introduction

This file is a different kind of dataset from a typical single-table CSV: it’s a sample extract from the U.S. General Services Administration’s Natural Gas Acquisition Program (NGAP) database, and it packs 11 separate relational tables into one Excel worksheet, stacked vertically and separated by blank rows and "Table Name: X" marker rows. Each table holds only a handful of illustrative sample rows (4-5 typically) rather than a full population — this looks like a schema/format sample rather than production data, so the right analysis here is different from a standard descriptive report: understanding what each table represents, how they relate to one another, and what data-quality issues are visible even in this small sample, rather than computing distributions across a large population.

The NGAP program is how GSA procures and manages natural gas contracts and delivery for federal facilities nationwide — the tables below cover contracts, contractors, local distribution companies (LDCs), facilities, points of contact, invoices, and monthly cost/usage records.

raw <- read_excel("natural_gas.xlsx", sheet = "NGAP Sample Data", col_names = FALSE, col_types = "text")

# The sheet stacks 11 tables vertically. Each table starts with a single-cell
# "Table Name: X" row, is followed by a header row, then data rows, and ends
# at the next fully-blank row. This loop slices the sheet into a named list
# of tidy data frames, one per table.
col1 <- raw[[1]]
table_starts <- which(str_detect(col1, "^Table Name:"))
table_names  <- str_trim(str_remove(col1[table_starts], "^Table Name:\\s*"))

is_blank_row <- function(row) all(is.na(row) | row == "")

extract_table <- function(start_row) {
  header_row <- start_row + 1
  header <- raw[header_row, ] %>% unlist() %>% unname()
  n_cols <- sum(!is.na(header))
  header <- header[1:n_cols]

  # find the first fully-blank row after the header to mark the table's end
  r <- header_row + 1
  while (r <= nrow(raw) && !is_blank_row(raw[r, ])) r <- r + 1
  end_row <- r - 1

  raw[(header_row + 1):end_row, 1:n_cols] %>%
    set_names(header) %>%
    mutate(across(everything(), ~ na_if(str_trim(.), "NULL")))
}

ngap <- table_starts %>%
  map(extract_table) %>%
  set_names(table_names)

About the data

tibble(
  Table = names(ngap),
  Columns = map_int(ngap, ncol),
  `Sample Rows` = map_int(ngap, nrow)
) %>%
  kable(caption = "Tables parsed out of the single worksheet") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Tables parsed out of the single worksheet
Table Columns Sample Rows
Contract 5 5
ContractFacilityLDC 6 5
Contractor 15 5
Facility 15 5
FacilityLDC 4 5
LDC 8 5
Master_Facility 2 5
POC 18 5
POCEntitySubContact 2 5
tblCostUsageTest 26 5
tblInvoiceMaster 7 5
tblInvoiceStatus 2 5

Every table here has 4 to 15 sample rows — far too few to support the kind of distribution or correlation analysis appropriate for a real population sample. That’s expected: this file exists to illustrate the shape of the NGAP database schema, not to summarize the full program. The analysis below treats it accordingly.


How the tables relate

tibble(
  Table = c("Contract", "ContractFacilityLDC", "Contractor", "Facility", "FacilityLDC", "LDC",
            "Master_Facility", "POC", "POCEntitySubContact", "tblCostUsageTest",
            "tblInvoiceMaster", "tblInvoiceStatus"),
  Purpose = c(
    "One row per natural gas supply contract, linked to a contractor",
    "Links a contract to a facility/LDC combination, with contract terms (cost, months, type)",
    "The natural gas suppliers/marketers GSA contracts with",
    "A federal facility (building) receiving natural gas service",
    "Links a facility to its Local Distribution Company, with estimated annual volume",
    "The Local Distribution Company (LDC) — the utility that physically delivers gas to a facility",
    "A master list of facility/region codes",
    "Point of Contact — the person at a facility responsible for energy/utility matters",
    "Sub-classification of a point of contact's role (e.g. Primary, Secondary)",
    "Monthly cost and usage records per facility, tracking both LDC and supplier-side cost/volume",
    "Invoice files received per facility, by year and month",
    "Status history for uploaded invoices"
  )
) %>%
  kable(caption = "Table purposes within the NGAP schema") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Table purposes within the NGAP schema
Table Purpose
Contract One row per natural gas supply contract, linked to a contractor
ContractFacilityLDC Links a contract to a facility/LDC combination, with contract terms (cost, months, type)
Contractor The natural gas suppliers/marketers GSA contracts with
Facility A federal facility (building) receiving natural gas service
FacilityLDC Links a facility to its Local Distribution Company, with estimated annual volume
LDC The Local Distribution Company (LDC) — the utility that physically delivers gas to a facility
Master_Facility A master list of facility/region codes
POC Point of Contact — the person at a facility responsible for energy/utility matters
POCEntitySubContact Sub-classification of a point of contact’s role (e.g. Primary, Secondary)
tblCostUsageTest Monthly cost and usage records per facility, tracking both LDC and supplier-side cost/volume
tblInvoiceMaster Invoice files received per facility, by year and month
tblInvoiceStatus Status history for uploaded invoices

The natural key that threads through most of these tables is FacilityCode (e.g. "VA0003ZZ") — it links Facility, FacilityLDC, tblCostUsageTest, and tblInvoiceMaster together, so that a single federal building’s gas contract, delivery utility, monthly costs, and invoices can all be traced from one code. Contract and Contractor are linked by ContractorId, and LDC/Contractor/POC all carry a StateAbbrev field that ties them loosely to geography.


Table-by-table contents

for (nm in names(ngap)) {
  cat("### ", nm, "\n\n")
  print(
    ngap[[nm]] %>%
      kable() %>%
      kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE, font_size = 12)
  )
  cat("\n\n")
}

Contract

ContractNumber ContractorId ExpiryDate CFileName ReactivationDt
GS-00P-02-BSC-0201 23 2004-09-30 00:00:00 NA 2004-09-30 00:00:00
GS-00P-02-BSC-0204 5 2003-10-31 00:00:00 NA NA
GS-00P-02-BSC-0206 6 2004-10-31 00:00:00 NA 2004-11-02 00:00:00
GS-00P-02-BSC-0207 4 2006-10-31 00:00:00 NA 2004-11-01 00:00:00
GS-00P-02-BSC-0209 7 2004-10-31 00:00:00 NA 2004-11-01 00:00:00

ContractFacilityLDC

ConFacCode ContractCost FlowDate ContractMonths ContractType Checked
316 4.1500000000000004 2002-11-01 00:00:00 24 Firm Fixed 1
340 4.5999999999999996 2002-11-01 00:00:00 24 Firm Fixed 1
274 4.2298999999999998 2002-11-01 00:00:00 24 NA 1
193 7.3879999999999999 2005-02-01 00:00:00 24 Fixed 1
610 1 2005-11-01 00:00:00 12 Variable 1

Contractor

StateAbbrev CName Address1 Address2 Address3 City Zip CuCurrent PaCurrent PoCurrent Contact Ext Fax email Status
OK Tiger Natural Gas 1422 E 71st Suite J NA NA Tulsa 74136 0 1 0 918-491-6998 0 918-491-6659 NA 1
GA PS Energy 2987 Clairmont Rd Suite 500 NA Atlanta 30329 1 0 0 404-321-5711 NA NA NA 1
OK Geary Energy 7712 S Yale Ave Suite 201 NA Tulsa 74136 1 0 0 918-523-2516 0 918-523-2522 NA 1
CO Utility Resource Solutions 1700 Lincoln St.  Suite 2530 NA Denver 80203 0 1 0 303-864-1919 0 NA NA 1
CO Select Natural Gas LLC 8122 Southpark Lane Suite 204 NA Littleton 80120 1 0 0 345-098-8890 456 NA NA 1

Facility

FacilityCode SCity SZip BName BCity BState BZip Mtelemetry Cucustomer Pacustomer PoCustomer InvoicesNeeded InvoiceMonths GSA MajorTypeId
AL030004 Talladega 35160 NA NA NA NA 0 0 0 1 0 NA 0 3
AR030004 Forrest City 72336-7000 NA Forrest City AR 72336-7000 0 0 0 1 0 NA 0 3
AR030104 na 75501 NA na AR 75501 0 0 0 1 0 NA 0 3
AR150232 Stuttgart 72160 NA Stuttgart AR 72160 0 1 0 0 NA NA 0 15
AZ010001 Mesa NA NA NA NA NA 0 1 0 0 0 NA 0 1

FacilityLDC

LDCId FacilityCode EstVolume Unit
39 NY0270ZZ 9923 Mmbtu
25 PA010501 52681 Mmbtu
17 MI010201 NA Mmbtu
20 NY0026ZZ 5621 Mmbtu
1 MA080502 14947 Mmbtu

LDC

StateAbbrev Lname Address1 Address2 Address3 City Zip Emergency
MA Columbia Gas of Massachusetts 2025 Roosevelt Ave NA NA Springfield 1104 800-688-6160
WA Cascade Natural Gas N/A N/A N/A N/A N/A N/A
NA Central Hudson Gas N/A N/A N/A N/A N/A N/A
OH Cincinnati Gas & Electric N/A N/A N/A N/A N/A N/A
AZ Citizens Utilities N/A N/A N/A N/A N/A N/A

Master_Facility

BldgNum RegnNum
AK0001ZZ NA
AK0005AK NA
AK0009ZZ NA
AK0013ZZ NA
AK0020ZZ NA

POC

StateAbbrev Title POCPosition FirstName LastName NickName Address1 Address2 Address3 City Zip Phone Ext Fax Emergency Email CellPhone Comments
NC NA Chief Financial Officer Albert Brese NA 300 Morgan St.  Suite 1402 NA Durham 27701 919-956-5541 NA 919- 956-7152 NA NA NA
AZ NA NA Albert Iamiceli NA 500 Highway 89 North NA NA Prescott 86313 928-717-7472 NA 716- 344-3303 NA NA NA
KS Mr.  Boiler Plant Operator Alex Strouhal NA 4101 S. 4th Street, Traff NA NA Leavenwoth 66048 913-682-2000 x2951 913- 758-4230 NA NA NA NA
DC NA Energy Coordinator Amy Hudson NA General Svcs Admin (WPY) 301 7th St. SW Room 7512 Washington 20024 202-708-4995 NA 202-401-3722 202-497-6164 202-497-6164 NA
VA NA NA Angel Perry NA 1234 I don’t know Ave. NA NA Petersburg 0 804-504-7200 1067 804-504-7227 804-504-7200 NA NA

POCEntitySubContact

ContSubId ContSubTypeName
9 Primary
9 Primary
9 Primary
10 Secondary
9 Primary

tblCostUsageTest

Flag_DC OLD_Penalty_Adj OLD_Supplier_Adj CostUsageId LDC_Vol LDC_Vol_Adj LDC_Org_Cost LDC_Cost_Adj LDC_Misc_Fee LDC_FinalVolume LDC_FinalCost LDC_UnitPrice LDC_UnitPrice_Original Supp_Vol Supp_Vol_Adj Supp_Org_Cost Supp_Cost_Adj Supp_Misc_fee Supp_FinalVolume Supp_FinalCost Supp_UnitPrice CU_Year CU_Month FacilityCode Total_FinalCost BT_Cost
NA NA NA 1 50.6 NA 139.97999999999999 NA NA 50.6 139.97999999999999 2.7664031620553402 NA 50.6 NA 350.15199999999999 0 NA 50.6 350.15199999999999 6.92 NA 1 VA0003ZZ 490.13 9.69
DC NA NA 2 100.98 NA 312.95 NA NA 100.98 312.95 3.0991285403050099 NA 100.98 NA 757.35 0 NA 100.98 757.35 7.5 NA 2 VA0003ZZ 1070.3 10.6
DC NA NA 3 237.02 NA 644.04 NA NA 237.02 644.04 2.7172390515568301 NA 237.02 NA 1777.65 0 NA 237.02 1777.65 7.5 NA 3 VA0003ZZ 2421.69 10.220000000000001
DC NA NA 4 335.04 NA 877.87 NA NA 335.04 877.87 2.6201946036294199 NA 335.04 NA 2512.8000000000002 0 NA 335.04 2512.8000000000002 7.5 NA 4 VA0003ZZ 3390.67 10.119999999999999
DC NA NA 5 242.33 NA 656.73 NA NA 242.33 656.73 2.7100647876862101 NA 242.33 NA 1817.4749999999999 0 NA 242.33 1817.4749999999999 7.5 NA 5 VA0003ZZ 2474.1999999999998 10.210000000000001

tblInvoiceMaster

FacilityCode InvoiceTypeID InvYear InvMonth InvFileName InvFileName1 InvFileName2
VA0422ZZ 1 2007 7 05-01-2007_11_33_22_3096668631.307.pdf NA NA
VA0422ZZ 2 2007 7 05-01-2007_11_38_47_3096668631.307.pdf NA NA
VA0422ZZ-01 1 2007 7 05-01-2007_11_51_41_3096915545.407.pdf NA NA
VA0422ZZ-01 2 2007 7 05-01-2007_11_52_24_3096915545.407.pdf NA NA
KY010101 2 2007 7 05-01-2007_01_31_18_saglexcoop407.pdf NA NA

tblInvoiceStatus

StatusDate Status
2007-05-01 00:00:00.000 Uploaded/Verified
2007-05-01 00:00:00.000 Uploaded/Verified
2007-05-01 00:00:00.000 Uploaded/Verified
2007-05-01 00:00:00.000 Uploaded/Verified
2007-05-01 00:00:00.000 Uploaded/Verified

The one table with enough data to analyze: monthly cost & usage

tblCostUsageTest is the only table with more than a few rows of numeric data, and it’s the closest thing in this file to a real analytic table: 5 consecutive monthly cost/usage records for a single facility (VA0003ZZ), tracking gas volume and cost separately on the LDC (delivery) side and the supplier (commodity) side.

cu <- ngap$tblCostUsageTest %>%
  mutate(across(c(LDC_Vol, LDC_Org_Cost, LDC_FinalVolume, LDC_FinalCost, LDC_UnitPrice,
                   Supp_Vol, Supp_Org_Cost, Supp_FinalVolume, Supp_FinalCost, Supp_UnitPrice,
                   CU_Month, Total_FinalCost, BT_Cost), as.numeric))

cu %>%
  select(CU_Month, FacilityCode, LDC_FinalVolume, LDC_FinalCost, LDC_UnitPrice,
         Supp_FinalVolume, Supp_FinalCost, Supp_UnitPrice, Total_FinalCost) %>%
  kable(caption = "Monthly cost and usage for facility VA0003ZZ (5-month sample)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Monthly cost and usage for facility VA0003ZZ (5-month sample)
CU_Month FacilityCode LDC_FinalVolume LDC_FinalCost LDC_UnitPrice Supp_FinalVolume Supp_FinalCost Supp_UnitPrice Total_FinalCost
1 VA0003ZZ 50.60 139.98 2.766403 50.60 350.152 6.92 490.13
2 VA0003ZZ 100.98 312.95 3.099128 100.98 757.350 7.50 1070.30
3 VA0003ZZ 237.02 644.04 2.717239 237.02 1777.650 7.50 2421.69
4 VA0003ZZ 335.04 877.87 2.620195 335.04 2512.800 7.50 3390.67
5 VA0003ZZ 242.33 656.73 2.710065 242.33 1817.475 7.50 2474.20
cu_long <- cu %>%
  select(CU_Month, LDC_UnitPrice, Supp_UnitPrice) %>%
  pivot_longer(-CU_Month, names_to = "Cost Component", values_to = "Unit Price") %>%
  mutate(`Cost Component` = recode(`Cost Component`,
                                    LDC_UnitPrice = "LDC (delivery) unit price",
                                    Supp_UnitPrice = "Supplier (commodity) unit price"))

ggplot(cu_long, aes(x = CU_Month, y = `Unit Price`, color = `Cost Component`)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2.5) +
  scale_x_continuous(breaks = 1:5) +
  scale_y_continuous(labels = dollar) +
  labs(title = "Unit price by cost component, Facility VA0003ZZ",
       x = "Month (sample sequence)", y = "Unit price ($/unit)", color = NULL) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

Two things stand out even in this small sample: the supplier (commodity) unit price is consistently more than double the LDC (delivery) unit price for every month shown — expected, since the commodity itself typically costs more than the utility’s delivery charge — and the supplier price jumps from $6.92 to $7.50 between month 1 and month 2 and then holds flat, while the LDC delivery price fluctuates modestly month to month. Total combined final cost across the 5-month sample sums to $9,846.99.


Data quality observations

Even at this small scale, several patterns are worth flagging for anyone building a pipeline on top of the full production NGAP database:

tibble(
  Issue = c(
    "Literal text \"NULL\" instead of a true empty/NA value",
    "Trailing whitespace padding on fixed-width codes",
    "Inconsistent missing-value conventions across tables",
    "Placeholder/junk data in free-text fields",
    "A ZIP code of \"0\" on one POC record"
  ),
  Example = c(
    "`CFileName`, `Address2`, many `Contractor`/`Facility`/`POC` fields",
    "`Master_Facility.BldgNum` values like `\"AK0001ZZ              \"`",
    "`LDC` uses literal `\"N/A\"` strings while other tables use `\"NULL\"`",
    "`POC.Address1` = \"1234 I don't know Ave.\" for contact Angel Perry",
    "`POC` record for Angel Perry (Petersburg, VA) has `Zip = 0`"
  )
) %>%
  kable(caption = "Data quality issues visible in this sample") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Data quality issues visible in this sample
Issue Example
Literal text “NULL” instead of a true empty/NA value CFileName, Address2, many Contractor/Facility/POC fields
Trailing whitespace padding on fixed-width codes Master_Facility.BldgNum values like "AK0001ZZ "
Inconsistent missing-value conventions across tables LDC uses literal "N/A" strings while other tables use "NULL"
Placeholder/junk data in free-text fields POC.Address1 = “1234 I don’t know Ave.” for contact Angel Perry
A ZIP code of “0” on one POC record POC record for Angel Perry (Petersburg, VA) has Zip = 0

The parsing step above already converts literal "NULL" strings to proper NA and trims whitespace from every field, which is a necessary first step before any of these tables could be loaded into a real database or joined reliably on their key fields — a code like "AK0001ZZ" with 14 trailing spaces will fail to match a clean "AK0001ZZ" in a join even though they look identical when printed.


Explore the data

cu %>%
  select(CU_Month, FacilityCode, LDC_FinalVolume, LDC_FinalCost, LDC_UnitPrice,
         Supp_FinalVolume, Supp_FinalCost, Supp_UnitPrice, Total_FinalCost, BT_Cost) %>%
  datatable(options = list(pageLength = 10, scrollX = TRUE), rownames = FALSE)

Key takeaways

  • This is a schema sample, not a population dataset: 11 relational tables with 4-15 rows each, meant to illustrate the structure of GSA’s NGAP database rather than support statistical analysis.
  • FacilityCode is the connective key across contracts, LDC assignments, cost/usage records, and invoices — a real analysis of the full database would join on this field.
  • The only table with enough rows to chart, tblCostUsageTest, shows supplier (commodity) gas consistently costing more than double the LDC (delivery) charge per unit, with the supplier price jumping once early in the sample window and then holding flat.
  • The sample itself demonstrates real-world data-quality issues worth fixing before production use: literal "NULL" text instead of true nulls, inconsistent missing-value conventions between tables, trailing whitespace on key fields, and at least one clearly invalid value (a 0 ZIP code).

Limitations

With only a handful of rows per table, no finding here should be read as representative of the full NGAP program — this file is best understood as a structural and data-quality reference. A meaningful analysis of natural gas costs, contract terms, or facility coverage would require the full production database behind this schema, most likely queried directly rather than distributed as a stacked-table spreadsheet extract.