Replicating Berger & Ofek (1995): Diversification's Effect on Firm Value¶
U.S.E. Finance Data Hub / WRDS Database: Compustat Historical Segments¶
Paper replicated: Berger, P. G. & Ofek, E. (1995). Diversification's Effect on Firm Value. Journal of Financial Economics, 37(1), 39–65.
Database used: Compustat Historical Segments (Data Hub guide)
What this notebook does¶
Berger & Ofek (1995) is the landmark paper on the "diversification discount." The authors show that multi-segment firms trade at a 13% to 15% discount relative to the sum of their segments' imputed stand-alone values. The paper has been cited over 4,500 times and launched an extensive literature on the costs and benefits of corporate diversification.
The key empirical strategy is to compare the actual market value of a diversified firm to the sum of "imputed values" of its individual segments, where each segment's imputed value is calculated by applying a single-segment (pure-play) industry multiplier to the segment's sales or assets.
- Pull segment-level data from Compustat Historical Segments (
comp_segments_hist_daily.wrds_segmerged) via the WRDS API - Pull firm-level data from Compustat North America (
comp.funda) for market values and totals - Construct the "excess value" measure by comparing actual firm value to the sum of imputed segment values
- Examine whether multi-segment firms trade at a discount relative to single-segment firms
- Compare our results to the published 1995 findings
Learning objectives¶
- Practice querying segment-level data from the WRDS Compustat Segments database
- Understand the difference between business segments (
BUSSEG/OPSEG) and geographic segments (GEOSEG) - Learn how to construct imputed values using industry multipliers, a technique widely used in corporate finance research
- Build intuition for the diversification discount and its empirical measurement
Requirements to run this notebook¶
- A valid WRDS account with Compustat Historical Segments and Compustat North America access
pip install wrds pandas numpy matplotlib seaborn- You will be prompted for your WRDS username/password the first time you connect (or set up a
.pgpassfile, see the WRDS Python guide)
1. Setup and WRDS connection¶
import wrds
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", palette="deep")
pd.set_option("display.max_columns", 50)
db = wrds.Connection()
2. Identifying the variables we need¶
This replication uses two WRDS databases together.
Compustat Historical Segments (comp_segments_hist_daily.wrds_segmerged) provides segment-level data.
| Variable | Description |
|---|---|
gvkey |
Firm identifier |
datadate |
Fiscal year end date |
stype |
Segment type (BUSSEG = business, OPSEG = operating, GEOSEG = geographic) |
snms |
Segment name |
sics1 |
Segment SIC code |
sales |
Segment sales |
ias |
Segment identifiable assets |
Compustat North America (comp.funda) provides firm-level totals needed for market value.
| Variable | Description |
|---|---|
at |
Total assets |
sale |
Total sales |
lt |
Total liabilities |
prcc_f |
Stock price (fiscal year end) |
csho |
Common shares outstanding |
We focus on business segments (stype IN ('BUSSEG', 'OPSEG')) since those are what Berger & Ofek use to define diversification. Geographic segments are excluded.
# Step 1: Pull segment-level data (business/operating segments only)
seg_query = """
SELECT gvkey, datadate, stype, snms, sics1, sales, ias
FROM comp_segments_hist_daily.wrds_segmerged
WHERE stype IN ('BUSSEG', 'OPSEG')
AND datadate >= '2015-01-01'
AND datadate <= '2023-12-31'
"""
seg = db.raw_sql(seg_query, date_cols=["datadate"])
seg["fyear"] = seg["datadate"].dt.year
print(f"Segment rows pulled: {len(seg):,}")
# Step 2: Pull firm-level data
firm_query = """
SELECT gvkey, fyear, datadate, at, sale, lt, prcc_f, csho, sich
FROM comp.funda
WHERE indfmt = 'INDL'
AND datafmt = 'STD'
AND consol = 'C'
AND popsrc = 'D'
AND fyear BETWEEN 2015 AND 2023
"""
firm = db.raw_sql(firm_query, date_cols=["datadate"])
print(f"Firm rows pulled: {len(firm):,}")
3. Cleaning the sample¶
# Clean segment data
seg = seg.dropna(subset=["sales"])
seg = seg[seg["sales"] > 0]
# Extract 2-digit SIC from segment SIC code
seg["sic2"] = seg["sics1"].astype(str).str[:2]
# Count segments per firm-year
seg_count = seg.groupby(["gvkey", "fyear"]).size().reset_index(name="n_segments")
# Clean firm data
firm = firm.dropna(subset=["at", "sale", "prcc_f", "csho", "lt"])
firm = firm[(firm["at"] > 0) & (firm["prcc_f"] > 0)]
firm = firm.drop_duplicates(subset=["gvkey", "fyear"])
# Market value of equity + book value of debt = total firm value
firm["mve"] = firm["prcc_f"] * firm["csho"]
firm["firm_value"] = firm["mve"] + firm["lt"]
# Exclude financial firms (SIC 6000-6999)
firm = firm[~((firm["sich"] >= 6000) & (firm["sich"] < 7000))]
# Merge segment count onto firm data
firm = firm.merge(seg_count, on=["gvkey", "fyear"], how="inner")
# Classify firms as single-segment or multi-segment
firm["diversified"] = (firm["n_segments"] > 1).astype(int)
print(f"Firms after cleaning: {len(firm):,}")
print(f"Single-segment: {(firm['diversified'] == 0).sum():,}")
print(f"Multi-segment: {(firm['diversified'] == 1).sum():,}")
4. Constructing the Excess Value measure¶
Berger & Ofek's "excess value" compares a diversified firm's actual value to the sum of its segments' imputed stand-alone values.
For each segment $i$ of firm $j$, the imputed value is:
$$\text{Imputed Value}_i = \text{Segment Sales}_i \times \text{Industry Median}\left(\frac{\text{Firm Value}}{\text{Sales}}\right)$$
where the industry median is computed across all single-segment firms in the same 2-digit SIC industry.
The firm's excess value is then:
$$\text{Excess Value}_j = \ln\left(\frac{\text{Actual Firm Value}_j}{\sum_i \text{Imputed Value}_i}\right)$$
A negative excess value indicates a diversification discount (the firm is worth less than the sum of its parts). A positive value indicates a diversification premium.
# Step 1: Compute industry median (Value/Sales) for single-segment firms
single = firm[firm["diversified"] == 0].copy()
single["sic2"] = single["sich"].astype(str).str[:2]
single["vs_ratio"] = single["firm_value"] / single["sale"]
# Winsorize the ratio to remove extreme outliers
lo, hi = single["vs_ratio"].quantile(0.01), single["vs_ratio"].quantile(0.99)
single["vs_ratio"] = single["vs_ratio"].clip(lo, hi)
industry_median = single.groupby(["sic2", "fyear"])["vs_ratio"].median().reset_index()
industry_median.columns = ["sic2", "fyear", "ind_median_vs"]
# Step 2: Merge industry multipliers onto segment data
seg_valued = seg.merge(industry_median, on=["sic2", "fyear"], how="inner")
seg_valued["imputed_value"] = seg_valued["sales"] * seg_valued["ind_median_vs"]
# Step 3: Sum imputed values per firm-year
imputed_sum = seg_valued.groupby(["gvkey", "fyear"])["imputed_value"].sum().reset_index()
imputed_sum.columns = ["gvkey", "fyear", "total_imputed_value"]
# Step 4: Merge and compute excess value
ev = firm.merge(imputed_sum, on=["gvkey", "fyear"], how="inner")
ev = ev[ev["total_imputed_value"] > 0]
ev["excess_value"] = np.log(ev["firm_value"] / ev["total_imputed_value"])
# Winsorize excess value
lo, hi = ev["excess_value"].quantile(0.01), ev["excess_value"].quantile(0.99)
ev["excess_value"] = ev["excess_value"].clip(lo, hi)
print(f"Firm-years with excess value: {len(ev):,}")
print(f"\nExcess value by diversification status:")
print(ev.groupby("diversified")["excess_value"].describe()[["count", "mean", "50%"]])
5. Visualizing the results¶
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Distribution of excess value by diversification status
single_ev = ev[ev["diversified"] == 0]["excess_value"]
multi_ev = ev[ev["diversified"] == 1]["excess_value"]
axes[0].hist(single_ev, bins=50, alpha=0.6, color="steelblue", label="Single-segment", density=True)
axes[0].hist(multi_ev, bins=50, alpha=0.6, color="firebrick", label="Multi-segment", density=True)
axes[0].axvline(0, color="black", linestyle="--", linewidth=0.8)
axes[0].set_title("Excess Value by Diversification Status")
axes[0].set_xlabel("Excess Value (log)")
axes[0].set_ylabel("Density")
axes[0].legend()
# Mean excess value over time
yearly_ev = ev.groupby(["fyear", "diversified"])["excess_value"].mean().unstack()
yearly_ev.columns = ["Single-segment", "Multi-segment"]
yearly_ev.plot(kind="bar", ax=axes[1], color=["steelblue", "firebrick"])
axes[1].axhline(0, color="black", linestyle="--", linewidth=0.8)
axes[1].set_title("Mean Excess Value Over Time")
axes[1].set_xlabel("Fiscal Year")
axes[1].set_ylabel("Mean Excess Value (log)")
axes[1].legend()
plt.tight_layout()
plt.show()
discount = ev[ev["diversified"] == 1]["excess_value"].mean()
print(f"Average diversification discount: {discount:.3f}")
print(f"This implies multi-segment firms trade at roughly {(np.exp(discount) - 1)*100:.1f}% relative to imputed value")
# Excess value by number of segments
ev_by_nseg = ev.groupby("n_segments")["excess_value"].agg(["mean", "count"])
ev_by_nseg = ev_by_nseg[ev_by_nseg["count"] >= 30] # require at least 30 obs
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(ev_by_nseg.index.astype(str), ev_by_nseg["mean"], color="steelblue")
ax.axhline(0, color="black", linestyle="--", linewidth=0.8)
ax.set_title("Mean Excess Value by Number of Business Segments")
ax.set_xlabel("Number of Segments")
ax.set_ylabel("Mean Excess Value (log)")
plt.tight_layout()
plt.show()
6. Comparing to Berger & Ofek (1995): What's the same, what's different¶
What replicates cleanly
The core methodology maps directly onto WRDS data. The segment-level sales and SIC codes come from wrds_segmerged, and the industry multipliers are computed from single-segment firms in Compustat North America. The excess value measure follows exactly the log-ratio formula in the paper. The 13% to 15% diversification discount documented in the original paper should be approximately visible in modern data as well, though the magnitude may differ.
Where a modern WRDS-based replication necessarily differs from the original
Segment reporting standards. Berger & Ofek use data from 1986 to 1991, when segment reporting followed SFAS 14. In 1997, SFAS 131 replaced SFAS 14 with the "management approach" to segment reporting, which changed how firms define and report segments. This structural break means segment counts and definitions are not directly comparable across the two eras.
Segment SIC codes. The
sics1field inwrds_segmergedreflects Compustat's assignment of SIC codes to segments based on 10-K descriptions. These assignments can be imprecise, especially after SFAS 131 when firms report segments based on internal management structure rather than industry lines. Some segments may have missing SIC codes, which we drop from the imputed value calculation.Sample composition. The original paper restricts to manufacturing and non-financial firms with clean segment data. Our sample is broader and includes all non-financial firms with at least one business segment reported. The original paper also requires that total segment sales sum to within 1% of firm-level total sales as a data quality check, which is a useful filter students can add.
Multiplier choice. Berger & Ofek use both sales and asset multipliers and also experiment with mean vs. median industry ratios. We use the sales multiplier with median ratios for simplicity, but extending to asset-based multipliers is a natural next step.
References¶
- Berger, P. G. & Ofek, E. (1995). Diversification's Effect on Firm Value. Journal of Financial Economics, 37(1), 39–65.
- WRDS Compustat Historical Segments guide: https://uufinance.github.io/data/wrds/databases/compustat-segments/
- WRDS Compustat Historical Segments access page: https://wrds-www.wharton.upenn.edu/pages/get-data/compustat-capital-iq-standard-poors/compustat/historical-segments-daily/
- WRDS Python/API setup guide: https://uufinance.github.io/data/wrds/notebook/
Prepared for the U.S.E. Finance Data Hub as a database-tutorial template.