*! version 1.0 19dec2025

Title

naics_to_ff -- Convert NAICS codes to Fama-French industry classifications

Syntax

naics_to_ff
varname
[if] [in]
, generate(newvar) [options]

Required
generate(newvar) name of new variable to create

Optional
scheme(#) industry classification scheme: 5, 10, 12, 17, 30, 38, 48, or 49; default is 48

labels apply value labels to the new variable

replace overwrite existing variable

nomissing force mapped SICs into "Other" if outside FF ranges

diagnostics display detailed mapping diagnostics

Mapping transparency
sicgen(newvar) save the mapped SIC code

sourcegen(newvar) save mapping source (dorn/census/manual/fallback/compustat)

weightgen(newvar) save employment weight from the Dorn crosswalk

weightgen() reports employment weights from the Dorn crosswalk (or market-cap
weights when compustat() is used) and is only populated under
method(maxweight); for method(skipaux) and method(first) it is missing.

Fallback
fallback(varname) use this variable (e.g., sich) when NAICS mapping fails

Comparison
compare(varname) compare with SIC-based classification

Method
method(string) SIC selection method: maxweight, skipaux, or first; default is maxweight

Compustat weights
compustat(filename) use Compustat annual data for market-cap weighted NAICS-to-SIC mapping

yearvar(varname) variable for time-varying weights (by fiscal year)

cyear(#) fixed calendar year for weights (e.g., 2020)

nofallback disable Dorn fallback for NAICS codes not in Compustat

Description

naics_to_ff converts North American Industry Classification System (NAICS)
codes to Fama-French industry classifications. This is a companion command to
sic_to_ff.

Requires sic_to_ff version 1.1 or later.

The command works in two steps:

1. Maps NAICS codes to SIC codes using the Dorn crosswalk (with employment
weights) supplemented by the Census 2002 concordance

2. Maps SIC codes to Fama-French industries using Ken French definitions

The command includes 1,463 NAICS code mappings covering NAICS 1997-2022 vintages:

Source Coverage

Dorn crosswalk 1,239 codes (includes NAICS 2022 codes recovered via concordance chaining)

Census concordance 222 codes (primarily agricultural production NAICS 111xxx-112xxx)

Manual mappings 2 codes for new industries (519130 Internet Publishing,
517919 All Other Telecom)

Six of the 1,463 codes map to auxiliary SIC codes (70001/40001) and are treated
as unmapped for FF classification; excluding these, the effective mapping count
is 1,457 (1,233 Dorn + 222 Census + 2 manual).

For NAICS codes that cannot be mapped (truncated codes, newer industries), use the
fallback() option to specify a SIC variable to use as a fallback.

Options

Required

generate(newvar) specifies the name of the new variable to be created
containing the Fama-French industry code.

Optional

scheme(#) specifies which Fama-French industry classification scheme
to use. Valid values are 5, 10, 12, 17, 30, 38, 48, or 49. The default is 48.

labels attaches value labels to the new variable.

replace permits overwriting an existing variable (also applies to sicgen,
sourcegen, and weightgen).

nomissing forces any mapped SIC code into "Other" if it falls outside
Ken French's explicit SIC ranges for the chosen scheme. This option cannot
assign industries to NAICS codes that are missing from the NAICS-to-SIC mapping.

diagnostics displays detailed mapping diagnostics including source
distribution, FF industry distribution, non-6-digit NAICS warnings, and top
unmapped NAICS codes.

Mapping transparency

sicgen(newvar) saves the intermediate SIC code mapped from the NAICS code.
Useful for auditing classifications and understanding the two-step mapping.

sourcegen(newvar) saves the mapping source: "dorn" for codes from the
Dorn crosswalk (employment-weighted), "census" for codes from the Census
2002 concordance, "manual" for manually assigned newer industry codes,
"fallback" when the fallback variable was used, or "compustat" when
the compustat() option was used for market-cap weighted mapping.

weightgen(newvar) saves the mapping weight. For default Dorn/Census mapping,
this is the employment weight from the Dorn crosswalk (missing for Census-sourced codes).
For compustat() mapping, this is the market-cap share used for SIC selection.
Note: Weights are only populated when method(maxweight) is used; with other
methods the weight variable will be missing (to avoid confusion about which SIC the
weight refers to).

Fallback

fallback(varname) specifies a variable to use as the SIC code when NAICS
mapping fails or is unavailable. This is useful for handling:

Missing NAICS Observations with blank or missing NAICS codes

Truncated NAICS Compustat often stores 2-5 digit NAICS codes (e.g., "212"
instead of "212xxx") which cannot be mapped to specific SIC codes

Newer industries Some NAICS 2017/2022 codes represent industries that
did not exist in 2002 and have no SIC equivalent

Invalid codes Placeholder codes like "999990" in Compustat

When specified, observations without a successful NAICS-to-SIC mapping (including
those with missing NAICS) will use the fallback variable as their SIC code for FF
classification. The sourcegen() variable will show "fallback" for these
observations. The fallback variable must contain valid SIC codes (numeric or
string, 4-digit, <= 9999). String values are automatically trimmed of whitespace.

Example: With Compustat data containing both naics and sich:

. naics_to_ff naics, gen(ff48) fallback(sich) diagnostics

This maps NAICS codes first, then uses sich for any observations where NAICS
is missing or mapping failed, achieving higher coverage than NAICS alone.

Comparison

compare(varname) computes the FF industry classification from a user-supplied
SIC variable and reports concordance statistics. This directly operationalizes
the validation comparison between NAICS-based and SIC-based classifications.
The SIC variable may be numeric or string. Note: the nomissing option does not
affect the compare SIC classification, ensuring an unbiased concordance measure.

Method

method(string) specifies which SIC selection strategy to use when a NAICS
code maps to multiple SIC codes:

maxweight (default) Choose the SIC with the highest employment weight
from the Dorn crosswalk. This is the most economically meaningful selection.

skipaux Like maxweight, but excludes auxiliary SIC codes (70001, 40001, etc.).
Use this for Compustat data where auxiliary codes produce missing FF values.
Note: NAICS codes that only map to auxiliary SICs will have missing values,
which may reduce coverage compared to maxweight.

first Uses the first-listed SIC from the original crosswalk ordering
(typically by SIC code), which may differ from maxweight for some NAICS codes.
Useful for sensitivity analysis or replicating non-weighted mappings.

Concrete Example: NAICS 493110 (General Warehousing)

This NAICS code maps to multiple SICs with different employment weights:

+
+
+
+
| SIC Code | Weight | Description |
+
+
+
+
| 70001 (auxiliary) | 0.46 | Services aux |
| 4225 | 0.32 | Gen Warehousing|
| 4226 | 0.22 | Special Warehousing|
+
+
+
+

Method selection results:

maxweight selects SIC 70001 (highest weight) -> FF = missing (aux code)

skipaux selects SIC 4225 (highest non-aux) -> FF48 = 40 (Transportation)

first selects SIC 4225 (lowest code #) -> FF48 = 40 (Transportation)

What are auxiliary SIC codes?

The SIC system includes "auxiliary establishments" - support units that serve
other establishments in the same company (e.g., corporate headquarters,
centralized warehouses, data processing centers). These don't have regular
4-digit SIC codes. In Dorn's crosswalk, they are coded as 5-digit placeholders:

20001 Auxiliary for Manufacturing (SIC sector 2)

40001 Auxiliary for Transportation/Utilities (SIC sector 4)

70001 Auxiliary for Services (SIC sector 7)

Since these codes exceed 9999, they fall outside Ken French's SIC ranges and
produce missing FF industry values. Use method(skipaux) to avoid them.

Compustat weights

compustat(filename) enables market-cap weighted NAICS-to-SIC mapping using
your own Compustat annual fundamental data file. This is an alternative to the
default Dorn employment-weighted crosswalk.

compustat() requires method(maxweight) (the default); other methods are
not supported.

Required Compustat variables:

naics NAICS code (string or numeric)

sich Historical SIC code

prcc_f Fiscal year-end stock price

csho Common shares outstanding

fyear Fiscal year

Market capitalization is calculated as prcc_f × csho. For each NAICS code
(and optionally each year), the command aggregates market cap by SIC code and
selects the SIC with the highest total market cap as the mapping.

yearvar(varname) specifies a variable in your current dataset (e.g., fyear)
to match against the Compustat mapping file. This enables time-varying weights:
each observation is mapped using the market-cap weights from its corresponding year.
Mutually exclusive with cyear().

cyear(#) specifies a fixed calendar year to use for all observations' weights.
For example, cyear(2020) uses market-cap weights computed from fiscal year 2020
Compustat data for all observations, regardless of their actual year.
Mutually exclusive with yearvar().

nofallback prevents falling back to the Dorn crosswalk for NAICS codes not
found in the Compustat mapping. By default (without this option), NAICS codes that
cannot be mapped via Compustat are mapped using the embedded Dorn/Census crosswalk.
With nofallback, unmapped NAICS codes will have missing SIC values.

Example: Time-varying market-cap weights

. naics_to_ff naics, gen(ff48) compustat("compustat_annual.dta") yearvar(fyear)

This maps each firm-year using the market-cap weighted NAICS-to-SIC from that year's
Compustat data. Firms with NAICS codes not in Compustat will fall back to Dorn weights.

Example: Fixed year weights

. naics_to_ff naics, gen(ff48) compustat("compustat_annual.dta") cyear(2020)

This maps all observations using 2020 market-cap weights. Useful for cross-sectional
analysis or when you want consistent weights across all observations.

Example: Compustat-only mapping (no Dorn fallback)

. naics_to_ff naics, gen(ff48) compustat("compustat_annual.dta") cyear(2020) nofallback

This uses only Compustat-derived weights. NAICS codes not found in Compustat 2020
will have missing FF industry values.

Stored results

naics_to_ff stores the following in r():

Scalars

r(N) total observations in sample

r(N_naics) observations with non-missing NAICS code

r(N_naics_mapped) NAICS codes successfully mapped to SIC

r(N_naics_unmapped) NAICS codes not mapped to SIC (given method)

r(N_ff_mapped) observations with FF industry assignment

r(N_ff_unmapped) SIC mapped but no FF assignment (SIC outside Ken French ranges)

r(naics_map_rate) percent of NAICS codes mapped to SIC

r(ff_map_rate) percent of observations with FF assignment

With fallback():

r(N_fallback) observations using fallback SIC variable

With compare():

r(N_concordant) observations with same FF from NAICS and SIC

r(N_discordant) observations with different FF from NAICS and SIC

r(N_comparable) observations with both FF assignments non-missing

r(concordance_rate) percent concordance

With compustat():

r(N_compustat_mapped) observations mapped via Compustat market-cap weights

r(N_dorn_fallback) observations using Dorn fallback (when not nofallback)

r(cyear) fixed year used (if cyear() specified)

r(nofallback) 1 if nofallback specified, 0 otherwise

Macros

r(scheme) FF scheme used

r(varname) name of the created variable

r(method) SIC selection method used

r(fallback_var) name of fallback variable (if fallback() specified)

r(compare_var) name of SIC variable compared (if compare() specified)

r(compustat_file) path to Compustat file (if compustat() specified)

r(yearvar) year variable name (if yearvar() specified)

Remarks

Validation

Using Compustat firm-years (2006-2024) with both SIC and NAICS codes available,
I find 93.3% concordance between SIC-based and NAICS-based FF48 classifications.
The 6.7% discordance is concentrated in Finance/Real Estate and Business Services
boundaries.

Examples

Basic usage:

. naics_to_ff naics, gen(ff48)

With labels and diagnostics:

. naics_to_ff naicsh, gen(ff48) labels diagnostics

. return list

Check mapping rate:

. display "Mapping rate: " r(naics_map_rate) "%"

Expose underlying mapping for auditing:

. naics_to_ff naicsh, gen(ff48) sicgen(mapped_sic) sourcegen(map_src)

. tab map_src

Compare NAICS-based vs SIC-based classifications (Compustat example):

. naics_to_ff naicsh, gen(ff48_naics) compare(sich)

. display "Concordance: " r(concordance_rate) "%"

Use skipaux method to avoid auxiliary SIC codes:

. naics_to_ff naicsh, gen(ff48) method(skipaux)

Sensitivity analysis across methods:

. naics_to_ff naicsh, gen(ff48_max) method(maxweight)

. naics_to_ff naicsh, gen(ff48_skip) method(skipaux) replace

. gen differ = (ff48_max != ff48_skip) if !missing(ff48_max, ff48_skip)

. tab differ

Technical Notes

1. Both numeric and string NAICS variables are accepted. String NAICS codes
are cleaned (whitespace and hyphens removed) before conversion. Hyphenated sector
ranges like 31-33 are treated as missing (with a warning) to avoid accidental
creation of bogus numeric codes. If string values contain non-numeric characters
that cannot be parsed, a warning is displayed and those observations are treated
as missing NAICS.

2. The mapping data (1,463 NAICS-to-SIC codes) is embedded directly in the
ado file. Six of these map to auxiliary SIC codes (>9999) and are treated as
unmapped for FF classification, leaving 1,457 effective mappings. No external
.dta file is required. The program preserves sort order (sortpreserve).

2a. The method() option controls SIC selection: maxweight (default)
uses the SIC with highest employment weight from the Dorn crosswalk; skipaux
uses the highest-weight non-auxiliary SIC (excludes 70001, 40001 which produce
missing FF values); first uses the first-listed SIC from the original
crosswalk ordering, which differs from maxweight for some NAICS codes.

2b. For Census-sourced NAICS codes (210 agricultural codes), only one SIC
mapping exists per code. The method() option has no effect on these codes.

3. Six NAICS codes have auxiliary SIC as their max-weight mapping (70001, 40001).
With the default method(maxweight), these will have missing FF values unless
nomissing is specified. With method(skipaux), four of these codes (493110,
493120, 493130, 493190) receive valid non-auxiliary SICs; two codes (551114, 950000)
have no non-auxiliary alternative and remain missing.

3a. Auxiliary SIC observations are counted in r(N_ff_unmapped) separately
from r(N_naics_unmapped).

4. Non-6-digit NAICS codes trigger a warning by default (e.g., 5-digit or 7-digit
codes). Additionally, fractional/decimal NAICS values (e.g., 541711.5) trigger a
separate warning. Both indicate potential data quality issues. The diagnostics
option provides additional detail on top unmapped codes.

5. NAICS vintage: The crosswalk is based on NAICS 1997/2002 codes. Datasets
using later NAICS vintages (2007, 2012, 2017, 2022) may have reduced coverage
due to new codes not in the mapping, and potential conceptual drift where code
meanings have shifted even when digits match.

References

Autor, Dorn, and Hanson. 2013. "The China Syndrome." AER 103(6): 2121-2168.

Kenneth R. French Data Library:
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html

Author

Kelvin Law, Nanyang Business School, NTU Singapore

Also see

Online: sic_to_ff, generate