# panelr This is an R package designed to aid in the analysis of panel data, designs in which the same group of respondents/entities are contacted/measured multiple times. `panelr` provides some useful infrastructure, like a `panel_data` object class, as well as automating some emerging methods for analyses of these data. [`wbm()`](https://panelr.jacob-long.com/reference/wbm.md) automates the “within-between” (also known as “between-within” and “hybrid”) specification that combines the desirable aspects of both fixed effects and random effects econometric models and fits them using the `lme4` package in the backend. Bayesian estimation of these models is supported by interfacing with the `brms` package ([`wbm_stan()`](https://panelr.jacob-long.com/reference/wbm_stan.md)) and GEE estimation via `geepack` ([`wbgee()`](https://panelr.jacob-long.com/reference/wbgee.md)). It also automates the fairly new “asymmetric effects” specification described by [Allison (2019)](https://journals.sagepub.com/doi/10.1177/2378023119826441) and supports estimation via GLS for linear asymmetric effects models ([`asym()`](https://panelr.jacob-long.com/reference/asym.md)) and via GEE for non-Gaussian models ([`asym_gee()`](https://panelr.jacob-long.com/reference/asym_gee.md)). ## Installation `panelr` is now available via CRAN. ``` r install.packages("panelr") ``` ## Usage ### `panel_data` frames While not strictly required, the best way to start is to declare your data as panel data. I’ll load the example data `WageData` to demonstrate. ``` r library(panelr) data("WageData") colnames(WageData) ``` [1] "exp" "wks" "occ" "ind" "south" "smsa" "ms" "fem" [9] "union" "ed" "blk" "lwage" "t" "id" The two key variables here are `t` and `id`. `t` is the wave of the survey the row of the data refers to while `id` is the survey respondent. This is a perfectly balanced data set, so there are 7 observations for each of the 595 respondents. We will use those two pieces of information to create a `panel_data` object. ``` r wages <- panel_data(WageData, id = id, wave = t) wages ``` ``` R # Panel data: 4,165 x 14 # entities: id [595] # wave variable: t [1, 2, 3, ... (7 waves)] id t exp wks occ ind south smsa ms fem union ed 1 1 1 3 32 0 0 1 0 1 0 0 9 2 1 2 4 43 0 0 1 0 1 0 0 9 3 1 3 5 40 0 0 1 0 1 0 0 9 4 1 4 6 39 0 0 1 0 1 0 0 9 5 1 5 7 42 0 1 1 0 1 0 0 9 6 1 6 8 35 0 1 1 0 1 0 0 9 7 1 7 9 32 0 1 1 0 1 0 0 9 8 2 1 30 34 1 0 0 0 1 0 0 11 9 2 2 31 27 1 0 0 0 1 0 0 11 10 2 3 32 33 1 1 0 0 1 0 1 11 # ... with 4,155 more rows, and 2 more variables: blk , lwage ``` We have to tell [`panel_data()`](https://panelr.jacob-long.com/reference/panel_data.md) which column refers to the unique identifiers for respondents/entities (the latter when you have something like countries or companies instead of people) and which column refers to the period/wave of data collection. Note that the resulting `panel_data` object will remember which of the columns is the ID column and which is the wave column. It will also fight you a bit when you do things that might have the side effect of dropping those columns or putting them out of time order. `panel_data` frames are modified tibbles ([`tibble` package](https://tibble.tidyverse.org/)) that are grouped by entity (i.e., the ID column). `panel_data` frames are meant to play nice with the [`tidyverse`](https://tidyverse.org/). Here’s a quick sample of how a tidy workflow with `panelr` can work: ``` r library(dplyr) data("WageData") # Create `panel_data` object wages <- panel_data(WageData, id = id, wave = t) %>% # Pass to mutate, which will calculate statistics groupwise when appropriate mutate( wage = exp(lwage), # reverse transform the log wage variable mean_wage_individual = mean(wage), # means calculated separately by entity lag_wage = lag(wage) # mutate() will calculate lagged values correctly ) %>% # Use `panelr`'s complete_data() to filter for entities that have # enough observations complete_data(wage, union, min.waves = 5) %>% # drop if there aren't 5 completions # You can use unpanel() if you need to do rowwise or columnwise operations unpanel() %>% mutate( mean_wage_grand = mean(wage) ) %>% # You'll need to convert back to panel_data if you want to keep using panelr functions panel_data(id = id, wave = t) ``` ### `wbm()` — the within-between model Anyone can fit a within-between model without the use of this package as it is just a particular specification of a multilevel model. With that said, it’s something that will require some programming and could be rather prone to error. In the best case, it is cumbersome and inefficient to create the necessary variables. [`wbm()`](https://panelr.jacob-long.com/reference/wbm.md) is the primary model-fitting function that you’ll use from this package and it fits within-between models for you, utilizing [`lme4`](https://cran.r-project.org/package=lme4) as a backend for estimation. A three-part model syntax is used that goes like this: `dv ~ varying_variables | invariant_variables | cross_level_interactions/random effects` It works like a typical formula otherwise. The bars just tell `panelr` how to treat the variables. Note also that you can specify random slopes using `lme4`-style syntax in the third part of the formula as well. A random intercept for the ID variable is included by default and doesn’t need to be specified in the formula. Lagged variables are supported as well through the [`lag()`](https://dplyr.tidyverse.org/reference/lead-lag.html) function. Unlike base R, `panelr` lags the variables correctly — wave 1 observations will have NA values for the lagged variable rather than taking the final wave value of the previous entity. Here we will specify a model using the `wages` data. We will predict logged wages (`lwage`) using two time-varying variables — lagged union membership (`union`) and contemporaneous weeks worked (`wks`) — along with a time-invariant predictor, a binary indicator for black race (`blk`). For demonstrative purposes, we’ll fit a random slope for `lag(union)` and a cross-level interaction between `blk` and `wks`. ``` r model <- wbm(lwage ~ lag(union) + wks | blk | blk * wks + (lag(union) | id), data = wages) summary(model) ``` ``` R MODEL INFO: Entities: 595 Time periods: 2-7 Dependent variable: lwage Model type: Linear mixed effects Specification: within-between MODEL FIT: AIC = 1427.04, BIC = 1495.03 Pseudo-R² (fixed effects) = 0.05 Pseudo-R² (total) = 0.75 Entity ICC = 0.73 WITHIN EFFECTS: --------------------------------------------------------- Est. S.E. t val. d.f. p ---------------- ------- ------ -------- --------- ------ lag(union) 0.04 0.04 1.24 88.17 0.22 wks -0.00 0.00 -1.51 2948.04 0.13 --------------------------------------------------------- BETWEEN EFFECTS: --------------------------------------------------------------- Est. S.E. t val. d.f. p ----------------------- ------- ------ -------- -------- ------ (Intercept) 6.20 0.24 25.89 571.97 0.00 imean(lag(union)) 0.03 0.04 0.72 593.27 0.47 imean(wks) 0.01 0.01 2.30 571.29 0.02 blk -0.35 0.06 -5.65 591.87 0.00 --------------------------------------------------------------- CROSS-LEVEL INTERACTIONS: ------------------------------------------------------ Est. S.E. t val. d.f. p ------------- ------- ------ -------- --------- ------ wks:blk -0.00 0.00 -1.06 2956.56 0.29 ------------------------------------------------------ p values calculated using Satterthwaite d.f. RANDOM EFFECTS: ------------------------------------- Group Parameter Std. Dev. ---------- -------------- ----------- id (Intercept) 0.3785 id lag(union) 0.24 Residual 0.2291 ------------------------------------- ``` Note that `imean()` is an internal function that calculates the individual-level mean, which represents the between-subjects effects of the time-varying predictors. The within effects are the time-varying predictors at the occasion level with the individual-level mean subtracted. If you want the model specified such that the occasion level predictors do not have the mean subtracted, use the `model = "contextual"` argument. The “contextual” label refers to the way these terms are normally interpreted when it is specified that way. You may also use `model = "between"` to fit what econometricians call the random effects model, which does not disaggregate the within- and between-entity variation. ### `widen_panel()` and `long_panel()` Two functions that should cover your bases for the tricky business of **reshaping** panel data are included. Sometimes, like for doing SEM-based analyses, you need your data in wide format — i.e., one row per entity. [`widen_panel()`](https://panelr.jacob-long.com/reference/widen_panel.md) makes that easy and should require minimal trial and error or thinking. Perhaps more often, your raw data are already in wide format and you need to get it into long format to do cool stuff like use [`wbm()`](https://panelr.jacob-long.com/reference/wbm.md). That can be very tricky, but [`long_panel()`](https://panelr.jacob-long.com/reference/long_panel.md) (I didn’t think `lengthen_panel()` or `longen_panel()` quite worked as names) should cover most situations. You tell it what the labels for periods are (e.g., does it range from `1` to `5`, `"A"` to `"E"`, or something else?), where they are located (before or after the variable’s name?), and what kinds of formatting go before/after it. Check out the vignette for more details and some worked examples. ## Contributing I’m happy to receive bug reports, suggestions, questions, and (most of all) contributions to fix problems and add features. I prefer you use the Github issues system over trying to reach out to me in other ways. Pull requests for contributions are encouraged. Please note that this project is released with a [Contributor Code of Conduct](https://github.com/jacob-long/panelr/blob/master/CONDUCT.md). By participating in this project you agree to abide by its terms. ## License The source code of this package is licensed under the [MIT License](https://opensource.org/license/mit). # Package index ## Regression models - [`wbm()`](https://panelr.jacob-long.com/reference/wbm.md) : Panel regression models fit via multilevel modeling - [`wbgee()`](https://panelr.jacob-long.com/reference/wbgee.md) : Panel regression models fit with GEE - [`fdm()`](https://panelr.jacob-long.com/reference/fdm.md) : Estimate first differences models using GLS - [`asym()`](https://panelr.jacob-long.com/reference/asym.md) : Estimate asymmetric effects models using first differences - [`asym_gee()`](https://panelr.jacob-long.com/reference/asym_gee.md) : Asymmetric effects models fit with GEE - [`wbm_stan()`](https://panelr.jacob-long.com/reference/wbm_stan.md) : Bayesian estimation of within-between models ## Panel data wrangling - [`panel_data()`](https://panelr.jacob-long.com/reference/panel_data.md) [`as_pdata.frame()`](https://panelr.jacob-long.com/reference/panel_data.md) [`as_panel_data()`](https://panelr.jacob-long.com/reference/panel_data.md) [`as_panel()`](https://panelr.jacob-long.com/reference/panel_data.md) : Create panel data frames - [`widen_panel()`](https://panelr.jacob-long.com/reference/widen_panel.md) : Convert long panel data to wide format - [`long_panel()`](https://panelr.jacob-long.com/reference/long_panel.md) : Convert wide panels to long format - [`summary(`*``*`)`](https://panelr.jacob-long.com/reference/summary.panel_data.md) : Summarize panel data frames - [`complete_data()`](https://panelr.jacob-long.com/reference/complete_data.md) : Filter out entities with too few observations - [`balance_panel()`](https://panelr.jacob-long.com/reference/balance_panel.md) : Balance panel data by filling gaps - [`has_gaps()`](https://panelr.jacob-long.com/reference/has_gaps.md) : Check if panel data has gaps - [`scan_gaps()`](https://panelr.jacob-long.com/reference/scan_gaps.md) : Scan for gaps in panel data - [`model_frame()`](https://panelr.jacob-long.com/reference/model_frame.md) : Make model frames for panel_data objects - [`unpanel()`](https://panelr.jacob-long.com/reference/unpanel.md) : Convert panel_data to regular data frame - [`is_panel()`](https://panelr.jacob-long.com/reference/is_panel.md) : Check if object is panel_data ## Model utilities - [`tidy(`*``*`)`](https://panelr.jacob-long.com/reference/wbm_tidiers.md) [`glance(`*``*`)`](https://panelr.jacob-long.com/reference/wbm_tidiers.md) [`glance(`*``*`)`](https://panelr.jacob-long.com/reference/wbm_tidiers.md) [`tidy(`*``*`)`](https://panelr.jacob-long.com/reference/wbm_tidiers.md) : Tidy methods for `wbm` models - [`tidy(`*``*`)`](https://panelr.jacob-long.com/reference/wbgee_tidiers.md) [`tidy(`*``*`)`](https://panelr.jacob-long.com/reference/wbgee_tidiers.md) [`glance(`*``*`)`](https://panelr.jacob-long.com/reference/wbgee_tidiers.md) : Tidy methods for `wbgee` models - [`tidy(`*``*`)`](https://panelr.jacob-long.com/reference/fdm_tidiers.md) [`tidy(`*``*`)`](https://panelr.jacob-long.com/reference/fdm_tidiers.md) [`glance(`*``*`)`](https://panelr.jacob-long.com/reference/fdm_tidiers.md) : Tidy methods for `fdm` and `asym` models - [`predict(`*``*`)`](https://panelr.jacob-long.com/reference/predict.wbm.md) [`simulate(`*``*`)`](https://panelr.jacob-long.com/reference/predict.wbm.md) : Predictions and simulations from within-between models - [`predict(`*``*`)`](https://panelr.jacob-long.com/reference/predict.wbgee.md) : Predictions and simulations from within-between GEE models - [`formula(`*``*`)`](https://panelr.jacob-long.com/reference/formula.wbm.md) : Retrieve model formulas from `wbm` objects - [`nobs(`*``*`)`](https://panelr.jacob-long.com/reference/nobs.wbm.md) : Number of observations used in `wbm` models - [`print(`*``*`)`](https://panelr.jacob-long.com/reference/print.WBFormula.md) : Print method for WBFormula - [`wbm-class`](https://panelr.jacob-long.com/reference/wbm-class.md) : Within-Between Model (`wbm`) class ## Other utilities - [`are_varying()`](https://panelr.jacob-long.com/reference/are_varying.md) : Check if variables are constant or variable over time. - [`make_wb_data()`](https://panelr.jacob-long.com/reference/make_wb_data.md) : Prepare data for within-between modeling - [`make_diff_data()`](https://panelr.jacob-long.com/reference/make_diff_data.md) : Generate differenced and asymmetric effects data - [`get_wave()`](https://panelr.jacob-long.com/reference/get_wave.md) [`get_id()`](https://panelr.jacob-long.com/reference/get_wave.md) [`get_periods()`](https://panelr.jacob-long.com/reference/get_wave.md) : Retrieve panel_data metadata - [`line_plot()`](https://panelr.jacob-long.com/reference/line_plot.md) : Plot trends in longitudinal variables - [`heise()`](https://panelr.jacob-long.com/reference/heise.md) : Estimate Heise stability and reliability coefficients ## Datasets - [`WageData`](https://panelr.jacob-long.com/reference/WageData.md) : Earnings data from the Panel Study of Income Dynamics - [`teen_poverty`](https://panelr.jacob-long.com/reference/teen_poverty.md) : National Longitudinal Survey of Youth teenage women poverty data - [`nlsy`](https://panelr.jacob-long.com/reference/nlsy.md) : National Longitudinal Survey of Youth data # Articles ### All vignettes - [Reshaping panel data with \`long_panel()\` and \`widen_panel()\`](https://panelr.jacob-long.com/articles/reshape.md): - [Introduction to the \`panelr\` package](https://panelr.jacob-long.com/articles/wbm.md):