The wealth detective

This year’s John Bates Clark Medal has been awarded to Gabriel Zucman, maestro of tax data and scourge of the ultra wealthy. Here is a visual retrospective of his work.

python
D3/OJS
Published

May 9, 2023

The French economist Gabriel Zucman has spent the last ten years calling out the shenanigans of the immensely rich. Profiled by Bloomberg as the “wealth detective”, he and frequent collaborators Thomas Piketty and Emmanuel Saez have wielded microdata and spreadsheets to bring economic inequality to the forefront of popular discourse, especially in America. Zucman is no ivory-tower academic: he is an unabashed activist and a ferocious tweeter (though his tweets have recently been purged). His style has rubbed many the wrong way, not least the Harvard Kennedy School, whose president and provost vetoed his candidacy for tenure “partly over fears that Mr. Zucman’s research could not support the arguments he was making in the political arena.”

No wonder then that his receipt of the 2023 John Bates Clark Medal — the second-most prestigious honor in economics after the Nobel — was met with some grumbling. Coverage from The Economist focused as much on his detractors as on his body of work. “American Economics Association Reaches a New Low” wrote David Henderson. And Tyler Cowen, a man who comments on everything, thought it not worthwhile to comment on Zucman’s win.

Gabriel Zucman, winner of the 2023 John Bates Clark Medal (image from gabriel-zucman.eu)

Yet for all the conservative ire he draws, Zucman merely follows the tradition of economist-crusaders like Milton Friedman. Lionized by Henderson as “brilliant” and “first-rate”, Friedman did not shy away from using his academic stature to forward deeply opinionated takes on the economy, couched in language that would probably be too strong for your typical two-handed economist. So too does Zucman, with the crucial addition of data. Heaps and heaps of it, in fact. While critics may quibble with his methods, his data ensures that debates can revolve around common ground.

This post looks at some of the major findings arising from his research so far. Let us start with the driving motivation behind much of his work, which is in documenting trends in inequality. Compared to GDP and other macroeconomic aggregates, inequality is much more challenging to measure due to the scarcity of data, particularly at the very top of the distribution. To this end, Piketty, Saez, and Zucman have provided an invaluable resource in the World Inequality Database, which harmonizes national accounts with tax and survey data to produce an assortment of inequality statistics for numerous countries. Their methodology is contestable; indeed, they refer to it as a “prototype”. But as Saez and Zucman write:

Economic statistics, like aggregate output or concentration of income, are not physical facts like mass or temperature. Instead, they are creations that reflect social, historical, and political contexts. How the data sources are assembled, what conceptual framework is used to combine them, what indicators are given prominence: all of these choices reflect objectives that must be made explicit and broadly discussed.

The fruits of their work as applied to the United States has painted a stark picture of inequality in recent decades. The chart below plots the share of wealth controlled by the bottom 90% against the share controlled by the top 0.1% (about 160,000 taxpayers in 2012). From 8.3% in 1980, the top 0.1% now hold about 20% of all wealth in the U.S.

Code
{
  const dim = ({ width: 790, height: 550 });
  const margin = ({ top: 60, bottom: 40, right: 50, left: 50 });
  
  const data = inequality;
  const X = d3.map(data, d => d.year);
  const Y = d3.map(data, d => d.share);
  const Z = d3.map(data, d => d.group);
  const I = d3.range(X.length);
  const xValues = [...new Set(X)];

  const xScaler = d3.scaleLinear()
    .domain([1912, 2030])
    .range([margin.left, dim.width - margin.right]);
  
  const yScaler = d3.scaleLinear()
    .domain([0, .35])
    .range([dim.height - margin.bottom, margin.top]);
  
  const line = d3.line()
    .curve(d3.curveBasis)
    .x(i => xScaler(X[i]))
    .y(i => yScaler(Y[i]));
  
  const container = d3.create("div");
  
  // Chart title //////////////////////////////////////////////////////////////
  
  container.append("div")
    .attr("class", "ojs-title")
    .html(`To those who have, more shall be given`);
    
  container.append("div")
    .attr("class", "ojs-subtitle")
    .style("margin-bottom", "1rem")
    .html(`Distribution of wealth by percentile, United States, 1917–2019`);
  
  // Chart canvas /////////////////////////////////////////////////////////////

  const svg = container.append("svg")
    .attr("width", dim.width)
    .attr("height", dim.height)
    .attr("viewBox", [0, 0, dim.width, dim.height])
    .attr("style", "max-width: 100%; height: auto; height: intrinsic;")
    .on("mouseenter", mouseentered)
    .on("mousemove", mousemoved)
    .on("mouseleave", mouseleft);
  
  svg.append("g")
    .append("rect")
    .attr("x", 0).attr("y", 0)
    .attr("width", dim.width).attr("height", dim.height)
    .style("fill", "#DCE7EB")
    .style("opacity", .6);
  
  // Axes /////////////////////////////////////////////////////////////////////
  
  const xAxis = d3.axisBottom(xScaler)
    .tickValues(Array.from({length: 11}, (_, i) => 1920 + i*10))
    .tickFormat(d3.format(".0f"))
    .tickSize(0)
    .tickPadding([10]);
    
  const yAxis = d3.axisLeft(yScaler)
    .ticks(5, ".0%")
    .tickSize(0)
    .tickPadding([10]);;
  
  svg.append("g")
    .attr("transform", `translate(0,${ dim.height - margin.bottom })`)
    .style("font-size", ".8rem")
    .call(xAxis)
    .call(g => g.select(".domain").remove())
    .append("path")
    .attr("d", d3.line()([[margin.left, 0], [dim.width - margin.right, 0]]))
    .style("fill", "none")
    .style("stroke", "black");
  
  svg.append("g")
    .attr("transform", `translate(${ margin.left },0)`)
    .style("font-size", ".8rem")
    .call(yAxis)
    .call(g => g.select(".domain").remove())
    .call(g => g.append("text")
      .attr("x", -margin.left)
      .attr("y", 25)
      .attr("dx", 10)
      .attr("fill", "black")
      .attr("text-anchor", "start")
      .style("font-size", ".9rem")
      .text("Share in total wealth"));
  
  // Lines and labels /////////////////////////////////////////////////////////
  
  const panel = svg.append("g")

  panel.append("g")
    .selectAll("path")
    .data(d3.group(I, i => Z[i]))
    .join("path")
      .style("fill", "none")
      .style("stroke", (d, i) => i === 0 ? colors.blue : colors.red)
      .style("stroke-width", 3)
      .attr("d", ([, I]) => line(I));
  
  panel.append("g")
    .append("text")
    .attr("x", xScaler(2020))
    .attr("y", yScaler(.227))
    .attr("text-anchor", "start")
    .attr("alignment-baseline", "middle")
    .style("font-size", ".9rem")
    .style("fill", colors.blue)
    .text("Bottom 90%")
    
  panel.append("g")
    .append("text")
    .attr("x", xScaler(2020))
    .attr("y", yScaler(.192))
    .attr("text-anchor", "start")
    .attr("alignment-baseline", "middle")
    .style("font-size", ".9rem")
    .style("fill", colors.red)
    .text("Top 0.1%")
  
  // Hover labels /////////////////////////////////////////////////////////////
  
  const marker = svg.append("g")
    .attr("display", "none")
  
  marker.append("path")
    .attr("d", d3.line()([[0, margin.top], [0, dim.height - margin.bottom]]))
    .style("stroke", "black")
    .style("stroke-width", 1)
  
  function mouseentered() {
    marker.attr("display", null);
    tooltip.style("display", "block");
  }
    
  function mousemoved(event) {
    const [xm, ym] = d3.pointer(event);
    const x = d3.least(xValues, x => Math.abs(xScaler(x) - xm));
    const i = X.map((d, i) => d === x ? i : "").filter(String);
    marker.attr("transform", `translate(${ xScaler(x) }, 0)`);
    tooltip
      .style("left", event.pageX + 18 + "px")
      .style("top", event.pageY + 18 + "px")
      .html(`<b>${x}</b><br>Bottom 90%: ${ d3.format(",.1%")(Y[i[0]]) }<br>Top 0.1%: ${ d3.format(",.1%")(Y[i[1]]) }`);
  }
  
  function mouseleft() {
    marker.attr("display", "none");
    tooltip.style("display", "none");
  }
  
  // Annotation ///////////////////////////////////////////////////////////////

  svg.append("g")
    .attr("transform", "translate(640, 360)")
    .append("text")
      .attr("text-anchor", "middle")
      .style("font-size", ".9rem")
      .style("fill", "black")
      .attr("x", 0).attr("y", 0)
    .append("tspan")
      .text("Wealth began to")
      .attr("x", 0).attr("y", 0)
    .append("tspan")
      .text("concentrate in the 1980s")
      .attr("x", 0).attr("y", 0).attr("dy", 20)
  
  svg.append("defs")
    .append("marker")
      .attr("id", "arrow-1")
      .attr("viewBox", "0 0 10 10")
      .attr("refX", 8).attr("refY", 5)
      .attr("markerWidth", 10)
      .attr("markerHeight", 10)
      .attr("orient", "auto")
    .append("path")
      .attr("d", "M0,1 L9,5 L0,9")
      .style("stroke-linejoin", "round")
      .style("stroke", "black")
      .style("stroke-width", 1)
      .style("fill", "none");
    
  svg.append("g")
    .append("path")
    .attr("d", () => {
      const path = d3.path();
      path.moveTo(620, 400);
      path.quadraticCurveTo(550, 500, 480, 410);
      return path;
    })
    .style("stroke", "black")
    .style("stroke-width", 1)
    .style("fill", "none")
    .attr("marker-end", "url(#arrow-1)");
  
  // Chart sources ////////////////////////////////////////////////////////////

  container.append("div")
    .attr("class", "ojs-source")
    .style("margin", ".5rem 0")
    .style("line-height", 1.25)
    .html(`Source: E. Saez and G. Zucman, "The Rise of Income and Wealth Inequality in America: Evidence from Distributional Macroeconomic Accounts", <i>Journal of Economic Perspectives</i>, vol. 34, no. 4 (2020).`);
    
  return container.node();
}

Since data on wealth ownership is not directly collected in America, the statistics above hinge on using income data — more widely available due to surveys and tax filings — to infer asset ownership. The technique, called income capitalization, uses reported interest and dividend income to guess at holdings of bonds and equities. This indirect approach is rough and requires plenty of further adjustments. Nevertheless, by showing how their results are consistent with other data sources1, Zucman and company make a compelling case for their methodology.

A weakness in this approach is its reliance on income data, which are typically self-reported. These may not be accurate, especially for the rich, who, for the purposes of avoiding taxes, have the incentive, opportunity, and capability to severely under-report their income. Zucman himself has investigated this for the case of Denmark, Norway, and Sweden. In a creative use of information windfalls, he and his collaborators matched administrative records with documented cases of hidden wealth as leaked in the Panama Papers, among other sources. They were then able to estimate what share of their tax liabilities Scandinavians were evading, disaggregated by wealth percentile. The chart below confirms the received wisdom that it is the rich — the ultra rich, to be exact — who are the most notorious tax evaders.

Code
{
  const dim = ({ width: 790, height: 550 });
  const margin = ({ top: 40, bottom: 60, right: 50, left: 105 });
  
  const data = evasion;
  const X = d3.map(data, d => d.evasion);
  const Y = d3.map(data, d => d.percentile);
  
  const xScaler = d3.scaleLinear()
    .domain([0, .30])
    .range([0, dim.width - margin.left - margin.right])
    
  const yScaler = d3.scaleBand()
    .domain(Y)
    .range([dim.height - margin.bottom, margin.top])
    .padding(.25);

  const container = d3.create("div");
  
  // Chart title //////////////////////////////////////////////////////////////
  
  container.append("div")
    .attr("class", "ojs-title")
    .html(`Render unto Ceaser...`);
    
  container.append("div")
    .attr("class", "ojs-subtitle")
    .style("margin-bottom", "1rem")
    .html(`Taxes evaded as a share of taxes owed by wealth percentile, Denmark, Norway, and Sweden`);
  
  // Chart canvas /////////////////////////////////////////////////////////////

  const svg = container.append("svg")
    .attr("width", dim.width)
    .attr("height", dim.height)
    .attr("viewBox", [0, 0, dim.width, dim.height])
    .attr("style", "max-width: 100%; height: auto; height: intrinsic;")
  
  svg.append("g")
    .append("rect")
    .attr("x", 0).attr("y", 0)
    .attr("width", dim.width).attr("height", dim.height)
    .style("fill", "#DCE7EB")
    .style("opacity", .6);
  
  // Axes /////////////////////////////////////////////////////////////////////
  
  const xAxis = d3.axisBottom(xScaler)
    .ticks(5, ".0%")
    .tickSize(0)
    .tickPadding([10]);
    
  const yAxis = d3.axisLeft(yScaler)
    .tickSize(0)
    .tickPadding([10]);
  
  svg.append("g")
    .attr("transform", `translate(${ margin.left },${ dim.height - margin.bottom })`)
    .style("font-size", ".8rem")
    .call(xAxis)
    .call(g => g.select(".domain").remove())
    .call(g => g.append("text")
      .attr("x", dim.width - margin.left)
      .attr("y", 0)
      .attr("dx", -20)
      .attr("dy", 30)
      .attr("fill", "black")
      .attr("text-anchor", "end")
      .attr("alignment-baseline", "hanging")
      .style("font-size", ".9rem")
      .text("Taxes evaded as a share of taxes owed"))
    .append("path")
      .attr("d", d3.line()([[0, 0], [dim.width - margin.left - margin.right, 0]]))
      .style("fill", "none")
      .style("stroke", "black");
  
  svg.append("g")
    .attr("transform", `translate(${ margin.left },0)`)
    .style("font-size", ".7rem")
    .call(yAxis)
    .call(g => g.select(".domain").remove())
    .call(g => g.append("text")
      .attr("x", -margin.left)
      .attr("y", 30)
      .attr("dx", 15)
      .attr("fill", "black")
      .attr("text-anchor", "start")
      .style("font-size", ".9rem")
      .text("Wealth pecentile"));
  
  // Bars /////////////////////////////////////////////////////////////////////
  
  svg.append("g")
    .attr("transform", `translate(${ margin.left },0)`)
    .selectAll("rect")
    .data(data)
    .join("rect")
    .attr("class", "evasion-bars")
    .attr("x", xScaler(0))
    .attr("y", (d, i) => yScaler(Y[i]))
    .attr("width", d => xScaler(d.evasion))
    .attr("height", yScaler.bandwidth())
    .style("fill", colors.blue)
    .on("mousemove", mousemoved)
    .on("mouseleave", mouseleft);
  
  function mousemoved(event, d) {
    d3.select(this)
      .transition().duration(50)
      .style("fill", colors.red);
    tooltip
      .style("display", "block")
      .style("left", event.pageX + 18 + "px")
      .style("top", event.pageY + 18 + "px")
      .text(d3.format(".1%")(d.evasion));
    d3.select(event.target).style("cursor", "pointer");
  }
  
  function mouseleft(event) {
    d3.selectAll("rect.evasion-bars")
      .transition().duration(200)
      .style("fill", colors.blue)
    tooltip.style("display", "none");
    d3.select(event.target).style("cursor", "default");
  }
  
  // Annotation ///////////////////////////////////////////////////////////////

  svg.append("g")
    .attr("transform", "translate(500, 200)")
    .append("text")
      .attr("text-anchor", "middle")
      .style("font-size", ".9rem")
      .style("fill", "black")
      .attr("x", 0).attr("y", 0)
    .append("tspan")
      .text("The top ")
      .attr("x", 0).attr("y", 0)
    .append("tspan")
      .text("0.01%")
      .style("font-weight", "bold")
    .append("tspan")
      .text(" evade ")
      .style("font-weight", "normal")
    .append("tspan")
      .text("26%")
      .style("font-weight", "bold")
      .style("fill", colors.red)
    .append("tspan")
      .text("of their tax obligations")
      .attr("x", 0).attr("y", 0).attr("dy", 20)
      .style("font-weight", "normal")
      .style("fill", "black")
  
  svg.append("defs")
    .append("marker")
      .attr("id", "arrow-2")
      .attr("viewBox", "0 0 10 10")
      .attr("refX", 8).attr("refY", 5)
      .attr("markerWidth", 10)
      .attr("markerHeight", 10)
      .attr("orient", "auto")
    .append("path")
      .attr("d", "M0,1 L9,5 L0,9")
      .style("stroke-linejoin", "round")
      .style("stroke", "black")
      .style("stroke-width", 1)
      .style("fill", "none");
    
  svg.append("g")
    .append("path")
    .attr("d", () => {
      const path = d3.path();
      path.moveTo(610, 200);
      path.quadraticCurveTo(700, 170, 650, 90);
      return path;
    })
    .style("stroke", "black")
    .style("stroke-width", 1)
    .style("fill", "none")
    .attr("marker-end", "url(#arrow-2)");

  // Chart sources ////////////////////////////////////////////////////////////

  container.append("div")
    .attr("class", "ojs-source")
    .style("margin", ".5rem 0")
    .style("line-height", 1.25)
    .html(`Source: A. Alstadsaeter, N. Johannesen, and G. Zucman, "Tax Evasion and Inequality", <i>American Economic Review</i>, vol. 109, no. 6 (2019).`);
    
  return container.node();
}

But nevermind evasion: do existing tax rates even subject the rich to their fair share of taxes? Let us return to the United States and consider what has so far been Zucman’s most controversial finding during the most contentious period of his career.

In late 2019, he and Saez engaged in a publicity campaign for their book, The Triumph of Injustice: How the Rich Dodge Taxes and How to Make Them Pay. You can tell from the title alone that this is a political manifesto as much as a summary of their academic work. Pieces in The New York Times and The Washington Post touted one of the book’s blockbuster claims, which is that the the very wealthiest of Americans — the top 0.00025% — today pay a lower share of their income in taxes than all other income groups. This came at the height of the Democratic primaries, where candidates like Bernie Sanders and Elizabeth Warren were competing for leadership of their party’s progressive wing. The Saez–Zucman claim naturally became a talking point.

Code
{
  const dim = ({ width: 790, height: 550 });
  const margin = ({ top: 40, bottom: 60, right: 65, left: 65 });
  
  const data = taxrate;
  const X = d3.map(data, d => d.group);
  const Y = d3.map(data, d => d.rate);
  const Z = d3.map(data, d => d.year);
  const I = d3.range(X.length);
  const xValues = [...new Set(X)];
  const n = [...new Set(Z)].length - 1;

  const xScaler = d3.scalePoint()
    .domain(xValues)
    .range([margin.left, dim.width - margin.right]);
  
  const yScaler = d3.scaleLinear()
    .domain([0, .72])
    .range([dim.height - margin.bottom, margin.top]);
  
  const line = d3.line()
    .curve(d3.curveBasis)
    .x(i => xScaler(X[i]))
    .y(i => yScaler(Y[i]));

  const container = d3.create("div");
  
  // Chart title //////////////////////////////////////////////////////////////
  
  container.append("div")
    .attr("class", "ojs-title")
    .html(`My yoke is easy, my burden is light`);
    
  container.append("div")
    .attr("class", "ojs-subtitle")
    .style("margin-bottom", "1rem")
    .html(`Average tax rate by income group, United States, 1950–2018`);
  
  // Chart canvas /////////////////////////////////////////////////////////////

  const svg = container.append("svg")
    .attr("width", dim.width)
    .attr("height", dim.height)
    .attr("viewBox", [0, 0, dim.width, dim.height])
    .attr("style", "max-width: 100%; height: auto; height: intrinsic;")
    .on("mouseenter", mouseentered)
    .on("mousemove", mousemoved)
    .on("mouseleave", mouseleft);
  
  svg.append("g")
    .append("rect")
    .attr("x", 0).attr("y", 0)
    .attr("width", dim.width).attr("height", dim.height)
    .style("fill", "#DCE7EB")
    .style("opacity", .6);
  
  // Axes /////////////////////////////////////////////////////////////////////
  
  const xAxis = svg.append("g")
    .attr("transform", `translate(0,${ dim.height - margin.bottom })`)
    .attr("text-anchor", "middle")
    .style("font-size", ".8rem")
    .call(g => g.append("text")
      .text("10th")
      .attr("x", xScaler("P10-20"))
      .attr("y", 0).attr("dy", 20))
    .call(g => g.append("text")
      .text("50th")
      .attr("x", xScaler("P50-60"))
      .attr("y", 0).attr("dy", 20))
    .call(g => g.append("text")
      .text("90th")
      .attr("x", xScaler("P90-95"))
      .attr("y", 0).attr("dy", 20))
    .call(g => g.append("text")
      .text("99.99+")
      .attr("x", xScaler("P99.99-top 400"))
      .attr("y", 0).attr("dy", 20))
    .call(g => g.append("path")
      .attr("d", d3.line()([[margin.left, 0], [dim.width - margin.right, 0]]))
      .style("fill", "none")
      .style("stroke", "black"))
    .call(g => g.append("text")
      .attr("x", margin.left + (dim.width - margin.left - margin.right) / 2)
      .attr("y", 0)
      .attr("dy", 30)
      .attr("alignment-baseline", "hanging")
      .style("font-size", ".9rem")
      .text("Income percentile"));
  
  const yAxis = d3.axisLeft(yScaler)
    .ticks(5, ".0%")
    .tickSize(0)
    .tickPadding([20]);
    
  svg.append("g")
    .attr("transform", `translate(${ margin.left },0)`)
    .style("font-size", ".8rem")
    .call(yAxis)
    .call(g => g.select(".domain").remove())
    .call(g => g.append("text")
      .attr("x", -margin.left)
      .attr("y", 25)
      .attr("dx", 15)
      .attr("fill", "black")
      .attr("text-anchor", "start")
      .style("font-size", ".9rem")
      .text("Share of pre-tax income"));
      
  // Lines and labels /////////////////////////////////////////////////////////
  
  const panel = svg.append("g")

  panel.append("g")
    .selectAll("path")
    .data(d3.group(I, i => Z[i]))
    .join("path")
      .style("fill", "none")
      .style("stroke", (d, i) => i === n ? colors.red : colors.blueLight)
      .style("stroke-width", 3)
      .attr("d", ([, I]) => line(I));
    
  panel.append("g")
    .selectAll("text")
    .data(data.filter(d => d.group === "Top 400"))
    .join("text")
    .text(d => d.year)
    .attr("x", d => xScaler(d.group))
    .attr("y", d => yScaler(d.rate))
    .attr("dx", 10)
    .attr("alignment-baseline", "middle")
    .style("fill", d => d.year === 2018 ? colors.red : colors.blue)
    .style("font-size", d => d.year === 2018 ? ".8rem" : ".7rem")
    .style("font-weight", d => d.year === 2018 ? "bold" : "normal")
  
  // Hover labels /////////////////////////////////////////////////////////////
  
  const marker = svg.append("g")
    .attr("display", "none");
  
  marker.append("path")
    .attr("d", d3.line()([[0, margin.top], [0, dim.height - margin.bottom]]))
    .style("stroke", "black")
    .style("stroke-width", 1);
  
  function mouseentered() {
    marker.attr("display", null);
    tooltip.style("display", "block");
  };
    
  function mousemoved(event) {
    const [xm, ym] = d3.pointer(event);
    const x = d3.least(xValues, x => Math.abs(xScaler(x) - xm));
    const i = X.map((d, i) => d === x ? i : "").filter(String);
    marker.attr("transform", `translate(${ xScaler(x) }, 0)`);
    tooltip
      .style("left", event.pageX + 18 + "px")
      .style("top", event.pageY + 18 + "px")
      .html(`
        <b>${x} tax rate</b><br>
        1950: ${ d3.format(",.1%")(Y[i[0]]) }<br>
        1980: ${ d3.format(",.1%")(Y[i[3]]) }<br>
        2018: ${ d3.format(",.1%")(Y[i[n]]) }
      `);
  };
  
  function mouseleft() {
    marker.attr("display", "none");
    tooltip.style("display", "none");
  };
  
  // Annotation ///////////////////////////////////////////////////////////////

  svg.append("g")
    .attr("transform", "translate(410, 200)")
    .append("text")
      .attr("text-anchor", "middle")
      .style("font-size", ".9rem")
      .style("fill", "black")
      .attr("x", 0).attr("y", 0)
    .append("tspan")
      .text("The ultra wealthy have seen an")
      .attr("x", 0).attr("y", 0)
    .append("tspan")
      .text("apparent fall in their tax burden")
      .attr("x", 0).attr("y", 0).attr("dy", 20)
    .append("tspan")
      .text("from ")
      .attr("x", 0).attr("y", 0).attr("dy", 40)
    .append("tspan")
      .text("70%")
      .style("font-weight", "bold")
    .append("tspan")
      .text(" in 1950 to ")
      .style("font-weight", "normal")
      .style("fill", "black")
    .append("tspan")
      .text("23%")
      .style("font-weight", "bold")
      .style("fill", colors.red)
    .append("tspan")
      .text(" in 2018")
      .style("font-weight", "normal")
      .style("fill", "black")
  
  svg.append("defs")
    .append("marker")
      .attr("id", "arrow-3")
      .attr("viewBox", "0 0 10 10")
      .attr("refX", 8).attr("refY", 5)
      .attr("markerWidth", 10)
      .attr("markerHeight", 10)
      .attr("orient", "auto")
    .append("path")
      .attr("d", "M0,1 L9,5 L0,9")
      .style("stroke-linejoin", "round")
      .style("stroke", "black")
      .style("stroke-width", 1)
      .style("fill", "none");
  
  svg.append("g")
    .call(g => g.append("path")
      .attr("d", () => {
        const path = d3.path();
        path.moveTo(460, 263);
        path.quadraticCurveTo(500, 450, 700, 360);
        return path;
    }))
    .call(g => g.append("path")
      .attr("d", () => {
        const path = d3.path();
        path.moveTo(470, 172);
        path.quadraticCurveTo(550, 0, 685, 40);
        return path;
    }))
    .style("stroke", "black")
    .style("stroke-width", 1)
    .style("fill", "none")
    .attr("marker-end", "url(#arrow-3)");
    
  // Chart sources ////////////////////////////////////////////////////////////

  container.append("div")
    .attr("class", "ojs-source")
    .style("margin", ".5rem 0")
    .style("line-height", 1.25)
    .html(`Source: E. Saez and G. Zucman, <i>The Triumph of Injustice</i>, 2019.`);
    
  return container.node();
}

But then came the backlash. The crux of the matter is the age-old question of tax incidence: who the tax is imposed on versus who ultimately pays for it. Particularly relevant here are corporate income taxes, which have fallen from over 50% in the 1950s to 35% today. These are statutorily paid by shareholders (who tend to be wealthy), but, due to behavioral adjustments by investors and executives, they may also be borne by workers and owners of non-corporate capital (who tend to be less wealthy). The progressiveness of the U.S. tax system changes drastically depending on whether these behavioral adjustments are incorporated. Most economists believe they should be while Saez and Zucman are arguing the other way.

Which party is more scientifically sound? That is a very hard question to answer. This and all of Zucman’s works involve politically charged issues for which many commentators have long ago staked a philosophical stance. When Zucman was a guest in his EconTalk podcast back in 2017, Russ Roberts, who has done no work on measuring inequality but identifies strongly as a classical liberal, opened the conversation by saying, “I’m skeptical of these findings. I find them hard to believe.” Likewise, it may be no coincidence that supporters like Paul Krugman sit on Zucman’s side of the ideological spectrum.

I think too much has been made of Gabriel Zucman’s supposed heterodoxy. With publications in the American Economic Review and the Quarterly Journal of Economics and tenure at UC Berkeley, he remains very much within the mainstream of the profession. A French Ha-Joon Chang, he is not. And with a John Bates Clark Medal under his belt, he now joins the likes of — wouldn’t you know it — Milton Friedman.

Data and cleaning scripts

D3 / Observable code

Code
inequality = FileAttachment("../../datasets/zucman/inequality.csv").csv({ typed: true });
evasion = FileAttachment("../../datasets/zucman/evasion.csv").csv({ typed: true });
taxrate = FileAttachment("../../datasets/zucman/taxrate.csv").csv({ typed: true });

colors = ({ blue: "#4889ab", blueLight: "#c0d7df", red: "#C85B89" })

tooltip = d3.select("body")
  .append("div")
  .attr("class", "toolTip")
  .style("display", "none")
  .style("position", "absolute")
  .style("z-index", 999)
  .style("width", 100)
  .style("height", "auto")
  .style("background", "#f7f7f7")
  .style("border", "1px solid #cecece")
  .style("opacity", .9)
  .style("padding", ".2em .45em")
  .style("font-size", ".85rem");

Footnotes

  1. One of these is the Fed’s own Distributional Financial Accounts statistics, published beginning 2019. Trends here have so far been consistent with those of Zucman et al.↩︎

Reuse