Mike Konczal is director of Macroeconomic Analysis at the Roosevelt Institute, where he focuses on full employment, inequality, and the role of public power in a democracy. He is the author of the recent Freedom from the Market: America’s Fight to Liberate Itself from the Grip of the Invisible Hand, and a co-author, with Joseph Stiglitz, of Rewriting the Rules of the American Economy. A former financial engineer, his writing has been featured in the New York Times, Washington Post, Rolling Stone, Vox, and more. A sought-after commentator on the U.S. economy, he has also appeared on CBS Sunday Morning, All Things Considered, Planet Money, Lovett or Leave It, and elsewhere.
Here are some of the monthly graphics I’m currently watching, along with the R code to reproduce them yourself.
Show the code
# Graphic - Three Onion Layer
# Hard code in weights
library(tidyverse)
library(janitor)
library(scales)
library(hrbrthemes)
library(ggrepel)
library(bea.R)
library(httr)
library(data.table)
library(magrittr)
<- function(beaKey, TableName, Frequency, Year, data_set_name = 'NIPA'){
get_NIPA_data <- list(
NIPA_request 'UserID' = beaKey ,
'Method' = 'GetData',
'datasetname' = data_set_name,
'TableName' = TableName,
'Frequency' = Frequency,
'Year' = Year,
'ResultFormat' = 'json'
);<- beaGet(NIPA_request, asWide = FALSE)
NIPA_data return(NIPA_data)
}
<- function(x){
BEA_date_monthly <- x %>%
x mutate(year = substr(TimePeriod, 1, 4)) %>%
mutate(month = substr(TimePeriod, 6,7))
$date <- paste(x$month, "01", x$year, sep="/")
x$date <- as.Date(x$date, "%m/%d/%Y")
x<- x %>% select(-month, -year)
x return(x)
}
<- read_csv("/Users/mkonczal/Documents/data_folder/BEA_key/BEA_key.csv")
beaKey <- as.character(beaKey)
beaKey # Table IDs
# https://www.bea.gov/system/files/2021-07/TablesRegisterPreview.txt
<- get_NIPA_data(beaKey, 'U20405', 'M', '2018,2019,2020,2021,2022,2023', data_set_name = 'NIUnderlyingDetail')
PCE_Weight <- BEA_date_monthly(PCE_Weight)
PCE_Weight
<- PCE_Weight %>% filter(SeriesCode == "DPCERC") %>%
PCE_Weight select(date, TotalGDP = DataValue) %>%
left_join(PCE_Weight, by="date") %>%
# The weight is approximated as nominal consumption shares as a percent of the total.
mutate(PCEweight = DataValue/TotalGDP) %>%
select(date, LineDescription, PCEweight)
<- get_NIPA_data(beaKey, 'U20404', 'M', '2018,2019,2020,2021,2022,2023', data_set_name = 'NIUnderlyingDetail')
pce <- BEA_date_monthly(pce)
pce
<- pce %>%
pce left_join(PCE_Weight, by=c('date' = 'date','LineDescription' = 'LineDescription'))
<- pce %>%
pce group_by(SeriesCode) %>%
mutate(DataValue_P1 = (DataValue - lag(DataValue,1))/lag(DataValue,1)) %>%
# Use the lagged weight for weighted share
mutate(WDataValue_P1 = DataValue_P1*lag(PCEweight,1)) %>%
mutate(WDataValue_P1a = (1+WDataValue_P1)^12-1) %>%
ungroup()
# With data in place, move to creating the onion chart
<- c("Goods","Gasoline and other energy goods","Food and beverages purchased for off-premises consumption","Services","Electricity and gas","Housing")
core_goods_fields
<- pce %>% filter(LineDescription %in% core_goods_fields) %>%
core_analysis filter(date >= "2018-01-01") %>%
select(date, LineDescription, WDataValue_P1) %>%
pivot_wider(names_from=LineDescription, values_from = WDataValue_P1) %>%
clean_names() %>%
mutate(core_goods = goods - food_and_beverages_purchased_for_off_premises_consumption - gasoline_and_other_energy_goods) %>%
mutate(core_services = services - electricity_and_gas - housing) %>%
mutate(core_inflation = core_goods + core_services) %>%
pivot_longer(-date, names_to = "item_name", values_to = "WDataValue_P1") %>%
filter(item_name %in% c("core_goods","core_services","housing")) %>%
mutate(item_nameF = factor(item_name, levels = c("core_goods", "housing", "core_services"))) %>%
mutate(WDataValue_P1a = (WDataValue_P1+1)^12-1) %>%
filter(WDataValue_P1a > -0.02)
levels(core_analysis$item_nameF)[levels(core_analysis$item_nameF) == "core_goods"] <-"Core Goods"
levels(core_analysis$item_nameF)[levels(core_analysis$item_nameF) == "core_services"] <-"Non-Housing Services"
levels(core_analysis$item_nameF)[levels(core_analysis$item_nameF) == "housing"] <-"Housing"
<- unique(sort(core_analysis$date,decreasing = TRUE))
onion_datebreaks <- onion_datebreaks[seq(1,length(onion_datebreaks),24)]
onion_datebreaks
ggplot(core_analysis, aes(x = date, y = WDataValue_P1a, fill=item_nameF)) +
geom_bar(stat = 'identity', size=0) +
theme_modern_rc() +
theme(legend.position = "none", legend.title = element_blank()) +
facet_grid(~item_nameF) +
labs(y = NULL,
x = NULL,
title = "The PCE inflation onion layers",
subtitle = "Monthly contribution to inflation",
caption ="BEA, NIPA Tables 2.4.4 and 2.4.5. Weights approximated as nominal consumption shares as a percent of the total.\nApril 2020 Core Services ex Housing value excluded as large negative outlier.") +
scale_fill_brewer(palette="RdPu", name = "item_name") +
scale_y_continuous(labels = percent) +
scale_x_date(date_labels = "%b\n%Y", breaks = onion_datebreaks) +
theme(axis.text.x = element_text(size=14), axis.text.y = element_text(size=14),
strip.text = element_text(face = "bold", color="white", hjust = 0.5, size = 10),
strip.background = element_blank(),
plot.title.position = "plot")
Show the code
# Download data
<- GET("https://download.bls.gov/pub/time.series/cu/cu.data.0.Current", user_agent("rortybomb@gmail.com")) %>%
cpi_data content(as = "text") %>%
fread()
<- cpi_data %>% clean_names()
cpi_data $value <- as.numeric(cpi_data$value)
cpi_data$series_id <- str_trim(cpi_data$series_id)
cpi_data$date <- paste(substr(cpi_data$period, 2,3), "01", substr(cpi_data$year, 3, 4), sep="/")
cpi_data$date <- as.Date(cpi_data$date, "%m/%d/%y")
cpi_data
<- GET("https://download.bls.gov/pub/time.series/cu/cu.series", user_agent("rortybomb@gmail.com")) %>%
series content(as = "text") %>%
fread()
<- series %>% clean_names()
series $series_id <- str_trim(series$series_id)
series
<- GET("https://download.bls.gov/pub/time.series/cu/cu.item", user_agent("rortybomb@gmail.com")) %>%
items content(as = "text") %>%
fread()
<- inner_join(series, items, by = c("item_code"))
series <- inner_join(cpi_data, series, by = c("series_id"))
cpi_data
# Graphic - Supercore
<- cpi_data %>% filter(seasonal == "S") %>%
supercore filter(item_name == "All items less food, shelter, energy, and used cars and trucks") %>%
select(item_name, date, value) %>%
mutate(pre_value = value[date=="2020-01-01"]/value[date=="2018-01-01"]) %>%
mutate(pre_value = pre_value^(12/24)-1) %>%
mutate(OneMonth = (value/lag(value,1))^12-1) %>%
mutate(ThreeMonth = (value/lag(value,3))^4-1) %>%
mutate(SixMonth = (value/lag(value,6))^2-1) %>%
filter(date >= "2018-01-01") %>%
pivot_longer(ThreeMonth:SixMonth, names_to = "time_length", values_to = "change") %>%
mutate(time_length = str_replace_all(time_length,"SixMonth", "6-Month Change")) %>%
mutate(time_length = str_replace_all(time_length,"ThreeMonth", "3-Month Change")) %>%
mutate(last_value = ifelse(date==max(date),change,NA)) %>%
mutate(last_value_pre = ifelse(date==max(date) & time_length=="3-Month Change",pre_value,NA)) %>%
mutate(recent_OneMonth = ifelse(date >= "2021-01-01" & time_length == "3-Month Change", OneMonth, NA))
<- unique(sort(supercore$date,decreasing = TRUE))
supercore_datebreaks <- supercore_datebreaks[seq(1,length(supercore_datebreaks),12)]
supercore_datebreaks
ggplot(supercore, aes(date, change, color=time_length, label=label_percent(accuracy=0.1)(last_value))) +
geom_line(size=1.2) +
theme_modern_rc() +
geom_line(aes(date,pre_value), linetype="dashed", color="#2D779C") +
geom_col(aes(date,recent_OneMonth), alpha=0.1, linewidth=0, show.legend = FALSE) +
labs(x="", y="",
title="Supercore CPI: All items less food, shelter, energy, used cars",
subtitle = "Trendline is Jan 2017 to Jan 2020, bars are 1-month change post 2021. All values annualized.",
caption = "") +
scale_y_continuous(labels = percent) +
scale_x_date(date_labels = "%b\n%Y", breaks = supercore_datebreaks) +
theme(legend.position = c(0.30,0.85), legend.title = element_blank()) +
theme(axis.text.x = element_text(size=14), axis.text.y = element_text(size=14),
legend.text = element_text(size=11)) +
scale_color_manual(values=c("#2D779C", "#A4CCCC")) +
geom_text_repel(show.legend=FALSE, nudge_x = 85, min.segment.length = Inf) +
geom_text(aes(date,last_value_pre, label=label_percent(accuracy=0.1)(pre_value)), show.legend=FALSE, nudge_x = 85, min.segment.length = Inf) +
theme(plot.title.position = "plot")
Show the code
library(tidyverse)
library(janitor)
library(scales)
library(hrbrthemes)
library(ggrepel)
<- GET("https://download.bls.gov/pub/time.series/jt/jt.data.1.AllItems", user_agent("rortybomb@gmail.com")) %>%
jolts_data content(as = "text") %>%
fread()
<- jolts_data %>%
jolts_data clean_names()
$value <- as.numeric(jolts_data$value)
jolts_data$series_id <- str_trim(jolts_data$series_id)
jolts_data$date <- paste(substr(jolts_data$period, 2,3), "01", jolts_data$year, sep="/")
jolts_data$date <- as.Date(jolts_data$date, "%m/%d/%Y")
jolts_data
<- GET("https://download.bls.gov/pub/time.series/ci/ci.data.1.AllData", user_agent("rortybomb@gmail.com")) %>%
eci_wages content(as = "text") %>%
fread() %>%
clean_names()
$value <- as.numeric(eci_wages$value)
eci_wages<- eci_wages %>%
eci_wages mutate(month = case_when(
== "Q01" ~ 3,
period == "Q02" ~ 6,
period == "Q03" ~ 9,
period == "Q04" ~ 12))
period $date <- paste(eci_wages$month, "01", eci_wages$year, sep="/")
eci_wages$date <- as.Date(eci_wages$date, "%m/%d/%Y")
eci_wages
# Graphic Set 1 : Sets Up Calculations
# Openings and Quits versus ECI Wages
<- jolts_data %>% filter(series_id == "JTS000000000000000QUR") %>% mutate(value = value/100) %>%
JoltsMerge select(date, quitsR = value)
<- eci_wages %>% filter(series_id == "CIS2020000000000I") %>% mutate(value = value/lag(value)-1) %>%
merged select(ECI_growth = value, date) %>%
inner_join(JoltsMerge, by=c("date")) %>%
filter(!is.na(ECI_growth)) %>%
mutate(Is_2021_to_2022 = (date >= "2021-01-01"))
<- merged %>% filter(date < "2021-01-01") %>% lm(ECI_growth ~ quitsR, data=.)
regression_prior
%>%
merged mutate(values_last = if_else(date >= "2022-01-01", date, as.Date(NA))) %>% mutate(values_last2 = as.character(format(values_last, "%b\n%Y"))) %>%
mutate(Is_2021_to_2022_v = if_else(date >= "2022-01-01",ECI_growth,as.numeric(NA))) %>%
ggplot(aes(quitsR, ECI_growth, color=Is_2021_to_2022, label=values_last2)) + geom_point() + theme_modern_rc() +
geom_abline(intercept = regression_prior$coefficients[1], slope=regression_prior$coefficients[2], color="#2D779C") +
geom_text_repel(size=3) +
geom_path(aes(quitsR,Is_2021_to_2022_v)) +
labs(x = "Quit Rate",
y = "ECI Private Wage Growth, Quarterly",
title = "Labor-market tightness: wage growth versus quit rates",
subtitle = "Regression line and blue points reflect 2001 to 2020.",
caption =NULL) +
theme(panel.grid.major.y = element_line(size=0.5)) +
theme(plot.title.position = "plot") +
scale_y_continuous(labels = percent) +
scale_x_continuous(labels = percent) +
theme(axis.title.x = element_text(size=14, color="white", vjust=-1.5), axis.title.y = element_text(size=14, angle = 90, vjust = 3),
plot.subtitle = element_text(size=12, color="white"), legend.position = "none") +
scale_color_manual(values=c("#2D779C", "#CC79A7"))
Praise for Freedom From the Market:
“The Roosevelt Institute’s Konczal is one of the warriors in this fight, arguing fiercely for the need to set much narrower limits on what is left to markets than has been the case in recent decades. A powerful polemic.”
- Martin Wolf, Financial Times
“By identifying an alternative grammar, one that is grounded in the American past, Freedom from the Market provides a way out of the political cul-de-sac created by the failure of the market to deliver on its promises of ‘freedom.’”
- Molly Michelmore, Democracy: A Journal of Ideas
“Freedom from the Market is an impressive book, easily one of the best I’ve read in the past several years. I cannot recommend it highly enough.”
- Matt Mazewski, Commonweal
“terrific book.”
- Jamelle Bouie, New York Times
“Freedom from the Market has the potential to be a very important book, focusing attention on the contested, messy but crucially important intersection between social movements and the state. It provides a set of ideas that people on both sides of that divide can learn from, and a lively alternative foundation to the deracinated technocratic notions of politics, in which good policy would somehow, magically, be politically self supporting, that has prevailed up until quite recently. Strongly recommended.”
—Henry Farrell, Crooked Timber