Skip to contents

The crosswalk maps each title to one role, but some titles mean different things in different organizations:

  • A PRESIDENT is the paid chief executive in one organization and a volunteer board chair in the next.
  • A VICE PRESIDENT may sit on the board or on staff.
  • A DIRECTOR may be a board member or a department head.

conditional_logic() (a wrapper for resolve_roles()) settles these with what the filer reported about each person: the Part VII checkboxes (trustee, officer, key employee, highly compensated), pay and hours. It also names the chief executive when no title does, and decides that a filing has none when the board runs the organization.

Before and after

Four example filings, built by hand. “Before” is the title-only answer from steps 07-08. “After” is step 09’s answer for each person.

Boxes: T trustee, O officer, K key employee, H highly compensated.

A large organization (990). org.leadership: designated
title pay hours boxes before: title only after: step 09 method
President & CEO 410,000 55 O CEO CEO (designated) rules
Chair of the Board 0 5 TO BOARD CHAIR BOARD CHAIR rules
Treasurer 0 3 TO BOARD TREASURER BOARD TREASURER rules
Director 0 2 T BOARD MEMBER BOARD MEMBER rules
Chief Financial Officer 260,000 50 O OFFICER OFFICER rules
VP of Development 190,000 45 K OFFICER OFFICER rules
Director of Nursing 175,000 40 H MANAGER MANAGER rules
Staff Physician 320,000 40 H PROFESSIONAL PROFESSIONAL rules

The checkboxes settle most of filing A. The CEO is designated by a CEO-level title in an officer position. The board titles with the trustee box are board seats. The paid staff keep the level their title implies.

B paid president, no CEO title (990). org.leadership: imputed
title pay hours boxes before: title only after: step 09 method
President 85,000 40 TO BOARD CHAIR CEO (imputed) rules
Vice President 0 2 T BOARD VICE CHAIR BOARD VICE CHAIR rules
Secretary 0 2 TO BOARD SECRETARY BOARD SECRETARY rules
Program Director 42,000 40 MANAGER MANAGER rules

No title in filing B names a CEO, but the president is paid $85,000 for 40 hours a week and ticks the officer box: the president is the imputed CEO. The unpaid vice president is a board seat (VICE CHAIR), not a staff officer.

C run by its board (990). org.leadership: board_governed
title pay hours boxes before: title only after: step 09 method
President 6,000 5 TO BOARD CHAIR BOARD CHAIR rules
Secretary 0 2 TO BOARD SECRETARY BOARD SECRETARY rules
Treasurer 0 2 TO BOARD TREASURER BOARD TREASURER rules
Bookkeeper 18,000 10 STAFF STAFF rules

Filing C pays its president a $6,000 stipend for 5 hours a week. A board title held under 10 hours a week for under $25,000 is a board seat, even with the officer box. No paid person qualifies as a leader (the bookkeeper is staff), so the filing is board_governed: no CEO is named.

D small organization (990-EZ). org.leadership: model
title pay hours boxes before: title only after: step 09 method
President 38,000 40 none on a 990-EZ BOARD CHAIR CEO (model) model (rules alone: CEO)
Administrator 30,000 40 none on a 990-EZ MANAGER OFFICER model (rules alone: MANAGER)
Director of Operations 0 10 none on a 990-EZ MANAGER BOARD MEMBER model (rules alone: MANAGER)
Secretary 0 2 none on a 990-EZ BOARD SECRETARY BOARD SECRETARY model (rules alone: BOARD)
Treasurer 0 2 none on a 990-EZ BOARD TREASURER BOARD TREASURER model (rules alone: BOARD)

A 990-EZ has no checkboxes. When the xgboost package is installed, roles on a 990-EZ come from the no-checkbox model, and role.method says model. Without xgboost, or with resolve_roles(use_model = FALSE), the rules decide. Here they disagree on two people: - Director of Operations. The rules read the title as a department head (MANAGER). The model reads an unpaid person working 10 hours a week in a small filing as a volunteer board member. - Administrator. The model makes the second paid full-timer an officer rather than a manager.

Rules and models explains the difference.

What it does

conditional_logic()  ->  resolve_roles()
|
+- 1. position, per row       trustee box = board; officer box = officer; both = board_officer;
|                             key-employee or high-comp box = staff; a paid CEO- or
|                             officer-level title counts as an officer without the box
+- 2. one role per person     a person with split titles (PRESIDENT & TREASURER) gets
|                             the most senior position; role.primary marks one row
+- 3. leadership, per filing  a CEO-level title in an officer position = designated CEO;
|                             else the highest-paid plausible leader = imputed CEO;
|                             else board_governed (paid people, no leader) or no_paid
+- 4. final role              CEO, OFFICER, MANAGER, PROFESSIONAL, STAFF or BOARD,
|                             and for BOARD the board role
+- 5. 990-EZ                  the no-checkbox model replaces the rules' answer

Three thresholds keep token pay from making a leader:

  • Imputed CEO. A leader with no CEO title must work at least 15 hours a week or earn at least $25,000, and have the officer box or a leadership title.
  • Board chair pattern. A paid board chair with the trustee box who works under 25 hours a week is not imputed as CEO.
  • Stipend rule. A board title held under 10 hours a week for under $25,000 is a board seat, with or without the officer box. On a 990-EZ the same holds for a board title paid a small fee.

Columns added

Column Values
role.final CEO, OFFICER, MANAGER, PROFESSIONAL, STAFF, BOARD
role.board for BOARD: CHAIR, VICE CHAIR, SECRETARY, TREASURER, MEMBER
role.ceo for CEO: designated, co-ceo, interim, imputed, model
role.position board, officer, board_officer, staff, unknown
role.source what settled the position: checkbox, title+pay, title
role.method rules, or model (990-EZ with xgboost installed)
role.primary TRUE on one row per person (count people with it)
org.leadership per filing: designated, imputed, model, board_governed, no_paid

The crosswalk’s own emp.level and board.role are kept: role.final is the resolved answer, the crosswalk columns the title-only default. Taxonomy defines each value.

Assets

Asset Type Used for Load it with
emp.level, board.role columns of title.taxonomy (from step 08) the title-only role each rule starts from get_title_taxonomy()
the role model xgboost model file, inst/extdata/role-model/ roles on 990-EZ filings predict_roles(); features from role_model_features()
system.file("extdata", "role-model", package = "titleclassifier") |> list.files()
#> [1] "feature-names.txt" "role-model.ubj"
# the model's input: one row per person-title, no checkboxes
X <- role_model_features(ex[ex$object.id == "D small organization (990-EZ)", ])
dim(X)
#> [1]   5 100
head(colnames(X), 12)
#>  [1] "comp"     "hours"    "paid"     "former"   "interim"  "co"      
#>  [7] "founder"  "exoff"    "n_titles" "n_people" "n_paid"   "pay_rank"
predict_roles(ex[ex$object.id == "D small organization (990-EZ)", ])
#> [1] "CEO"     "OFFICER" "BOARD"   "BOARD"   "BOARD"

Try it on the demo data

data(tinypartvii)
set.seed(8)
d <- dplyr::sample_n(tinypartvii, 1500)
df <- d |>
  standardize_df() |> remove_dates() |> standardize_conj() |>
  split_titles() |> standardize_spelling() |> gen_status_codes() |>
  standardize_titles() |> categorize_titles() |> conditional_logic()
#> [OK] standardize df step complete
#> [OK] remove dates step complete
#> [OK] standardize conjunctions step complete
#> [OK] split titles step complete
#> [OK] standardize spelling step complete
#> [OK] generate status codes step complete
#> [OK] standardize titles step complete
#> [OK] categorize titles step complete
#> 
#> [OK] conditional logic step complete

people <- dplyr::filter(df, role.primary)
table(people$role.final)
#> 
#>        BOARD          CEO      MANAGER      OFFICER PROFESSIONAL        STAFF 
#>         1333          120            2           24            1           20
table(people$formtype, people$role.method)
#>        
#>         model rules
#>   990       0  1082
#>   990EZ   418     0

Titles the crosswalk files as board seats but that resolve otherwise, here a paid board president who runs the organization:

people |>
  dplyr::filter(title.standard == "BOARD PRESIDENT", role.final != "BOARD") |>
  dplyr::select(org.name, title.raw, tot.comp, tot.hours, role.final, role.ceo) |>
  head(8)
#>                                                              org.name
#> 1                                 Noah Webster Educational Foundation
#> 2                            LABORERS' INTERNATIONAL UNION LOCAL 1092
#> 3 LOCAL NO 1 ELEVATOR CONSTRUCTORS UNIONOF NEW YORK CITY AND VICINITY
#> 4                              JUNIOR ACHIEVEMENT OF THE OCOEE REGION
#> 5                            AMERICAN FEDERATION OF LABOROHIO AFL-CIO
#> 6                                            THE KEYSTONE PROJECT INC
#> 7                                       METROPORT CHAMBER OF COMMERCE
#> 8                            Nonprofit Holistic Community Care Center
#>                title.raw tot.comp tot.hours role.final role.ceo
#> 1              President    55015        30        CEO  imputed
#> 2              PRESIDENT   255118        40        CEO  imputed
#> 3 PRESIDENT/BUSINESS MGR   423136        55        CEO  imputed
#> 4              PRESIDENT    62704        40        CEO  imputed
#> 5              PRESIDENT   302557        40        CEO  imputed
#> 6              PRESIDENT   106291        40        CEO  imputed
#> 7           PRESIDENT, C   202089        50        CEO  imputed
#> 8              President    14481        40        CEO    model

How each filing is led:

df |>
  dplyr::distinct(object.id, formtype, org.leadership) |>
  dplyr::count(formtype, org.leadership)
#>   formtype org.leadership   n
#> 1      990 board_governed  53
#> 2      990     designated  32
#> 3      990        imputed  66
#> 4      990        no_paid 775
#> 5    990EZ board_governed  20
#> 6    990EZ     designated  12
#> 7    990EZ          model  10
#> 8    990EZ        no_paid 352

Input: the output of categorize_titles(). Output: the same rows with the role columns added. See Performance for how often the roles agree with hand labels.