This RMarkdown replicates the core analyses from “China’s Ideological Spectrum”. The replication, conducted by Jinwen Wu, a predoctoral fellow at Stanford University, was supervised by Professor Yiqing Xu. It summarizes the main data analyses from the article; for a comprehensive understanding of the ideas presented, please refer to the original paper.

Click the Code button at the top right and select Show All Code to reveal all codes used in this RMarkdown. Click Show in paragraphs to reveal the code used to generate a finding. The R code and data files used in this RMarkdown can be downloaded here. The original replication files can be downloaded here.


The study examines how the policy preferences of the Chinese public in political, economic, and social domains are structured and constrained within an authoritarian context. Drawing on a nationwide online survey (n = 460,532; reweighted to n = 10,000 for improved representativeness) conducted on the Zuobiao (Chinese Political Compass) platform, the authors identify a three-dimensional ideological spectrum that contrasts with the tighter ideological constraints observed in competitive democracies.

Their main findings include:

  • The political preferences of the Chinese public exhibit weaker cross-issue constraint than in competitive democracies
  • The three latent dimensions of the spectrum capture distinct clusters of political liberalism, market orientation & traditionalism, and nationalism
  • Individual-level ideology on these dimensions correlates with socioeconomic status and regional economic development

1 Conceptual Framework

Following Converse (1964), Pan and Xu (2018) define ideology as a system of constrained and structured preferences across issue domains. They focus on political ideology and investigate how public preferences are configured and the extent to which the configuration is bound by constraints.

1.1 Configuration and Constraint

Under the definition, ideology can be conceptualized in terms of dimensionality (how many independent belief axes exist) and constraint (how well attitudes in one domain predict others). Configuration represents the degree to which preferences across different issue domains align along consistent patterns (e.g., whether attitudes toward political institutions correlate with views on economic policy or nationalism). By identifying the configuration of preferences, the authors argue that ideological cleavages do exist in autocracies. Constraint is the extent to which preferences in one domain (e.g., political values) predict preferences in another (e.g., economic policy or social norms). It reflects the internal coherence of belief systems.

1.2 Coherent and Constrained Preferences in Autocracies

In democracies, ideology has been studied through diverse surveys and more recently, nonsurvey methods such as campaign contributions (Bonica 2014), Twitter networks (Barberá 2015), and representation in the media (Groseclose and Milyo 2005). However, in authoritarian regimes, the structure of ideology remains understudied, due in part to the assumption that public preferences are unorganized or politically inconsequential.

Pan and Xu (2018) challenge this view by showing that (1) people in authoritarian regimes can still hold coherent ideological orientations, and (2) ideology reveals latent cleavages or sources of support (and opposition). Ideology in autocracies can also inform regime stability, elite conflict, or crisis potential and thus, matters even without electoral competition.

In autocracies, ideology is not reducible to a simple pro- vs. anti-regime cleavage. The authors focus on China, structure an ideology system along latent dimensions with confirmatory factor analysis (CFA). China ideology spectrum contains three dimensions:

  • Political: Liberal vs. conservative institutional and civil values.
  • Economic/Social: Promarket/nontraditional vs. antimarket/traditional.
  • Nationalist: Nationalist vs. nonnationalist views.

Moving beyond traditional measures of regime support, they show that policy preferences among the Chinese public are multidimensional but coherent—political liberalism, market orientation & western social values, non-nationalistic positions often go together. Moreover, politically liberal, promarket, and nonnationalist preferences are more prevalent among the educated, wealthy, and residents of economically developed regions (such as Beijing and Shanghai); politically conservative, antimarket, and nationalist preferences are more common among the less educated and poorer provinces.

2 Data & Measurement

The Zuobiao online survey collected responses from 460,532 participants between 2012 and 2014. The survey consists of 50 attitudinal questions related to politics, economics, and social values, organized into seven substantive categories: political institutions, individual freedom, market economy, capital and labor, economic sovereignty, nationalism, and traditionalism. These categories were grounded in elite discourse, Marxist and liberal traditions, and popular debates over China’s reform trajectory. Since Zuobiao is an opt-in online survey and thus, not nationally representative, it overrepresents the younger, more urban, and more educated population.

To improve representitiveness of the sample, Pan and Xu (2018) construct a weighted, demographically adjusted subsample of 10,000 respondents based on province, gender, and age. This adjusted sample serves as the main dataset for guaging the structure of policy preferences in China. The authors validate the results with the third wave of the Asian Barometer Survey (ABS), a nationally representative sample that includes overlapping questions on political and social values with Zuobiao. The findings are consistent across these datasets.

Below are the datasets used in the tutorial:

File Objects created Usage
data/sample10K.RData d (10 000‐respondent subsample of the Zuobiao survey) PCA, predictive-power curves (Figure 4)
output/result_cfa_search.RData results.final (grid-search results for CFA dimensionality) Model selection table and χ²/RMSEA scatterplot (Figure 5)
output/result_cfa.RData d with latent-trait scores lt1–lt3, PC1, province IDs pair plot of latent traits and province-level averages
output/result_cfa_ind.RData ses.edu, ses.income, ses.age Zuobiao point-estimate + CI arrays for education, income, age (Figures 10–11, top rows)
output/result_abs_boot.RData out.edu, out.inc, out.age, out.age.urban, out.age.rural ABS bootstrap summaries for the same covariates (Figures 10–11, bottom rows)
data/prov_corr.dta prov.corr (province-level economics: income, openness, urban, provgb) Merged with province means of lt1 to study macro correlates (final panel set)

3 Replicating the Main Findings

3.1 Installing Packages

Several R packages are required for the data analysis and visualization. The code chunk below checks for all required packages and installs the missing ones.

Packages: “dplyr”, “foreign”, “GGally”, “ggplot2”, “knitr”, “mvtnorm”, “plyr”, “psych”

options(repos = c(CRAN = "https://cran.r-project.org"))

packages <- c("dplyr", "foreign", "GGally", "ggplot2", "knitr", "mvtnorm", "plyr", "psych")

for (pkg in packages) {
  if (!requireNamespace(pkg, quietly = TRUE)) {
    install.packages(pkg)
  }
  library(pkg, character.only = TRUE)
}

Next, load all required datasets.

paths <- list(
  sample10k   = "data/sample10K.RData",
  prov_corr   = "data/prov_corr.dta",
  cfa_search  = "output/result_cfa_search.RData",
  cfa_full    = "output/result_cfa.RData",
  cfa_ind     = "output/result_cfa_ind.RData",
  abs_boot    = "output/result_abs_boot.RData"
)

## keep Zuobiao 10 K survey as d10K, then bring in the full CFA file (d)
load(paths$sample10k)          # creates d
d10K <- d ; rm(d)

load(paths$cfa_full)           # creates d with lt1–lt3, PC1, provgb
load(paths$cfa_search)         # results.final
load(paths$cfa_ind)            # ses.*
load(paths$abs_boot)           # out.*
prov.corr <- read.dta(paths$prov_corr)

3.2 Conceptualizing ideology

Figure 1 presents hypothetical illustrations for dimensionality of ideological configurations. The left plot depicts a 2D orthogonal configuration where two dimensions (e.g., individualism vs. collectivism and economic interventionism) are independent. The middle plot shows a 1D configuration where the two dimensions are perfectly correlated such that one latent dimension explains all variation (e.g., collectivists also uniformly favor interventionism). The right plot presents a 2D non-orthogonal configuration where preferences on one dimension are correlated with the other but not perfectly. There are still distinct cleavages along both dimensions, but they are interrelated.

layout(matrix(1:3, nrow = 1), widths = c(1, 1, 1))
par(oma = c(1, 1, 2, 1), mar = c(1, 1, 1, 1))

plot_base <- function(data, overlay = NULL) {
  plot(1, type = "n", xlim = c(-2, 2), ylim = c(-2, 2),
       xlab = "", ylab = "", axes = FALSE)
  if (!is.null(overlay))
    lines(overlay$x, overlay$y, col = 4, lwd = 2, lty = overlay$lty)
  points(data, col = "gray30", pch = 1, cex = 1)
  arrows(-1.5, 0, 1.5, 0, code = 3, lwd = 3)
  arrows(0, -1.5, 0, 1.5, code = 3, lwd = 3)
  rect(-1.55, -1.55, 1.55, 1.55, border = NA, lty = 3)   # frame
  text(-1.3, 0.3, "Collectivism",              cex = 1.2)
  text( 1.3, 0.3, "Individualism",             cex = 1.2)
  text(0, -1.7, "Economic\nInterventionism",   cex = 1.2)
  text(0,  1.7, "Economic\nNon-intervention",  cex = 1.2)
}

## Plot 1
set.seed(1234)
x1 <- matrix(rnorm(400, 0, 0.5), ncol = 2)
plot_base(x1)

## Plot 2
x2 <- rmvnorm(200, sigma = matrix(c(1, 0.995, 0.995, 1) * 0.22, 2))
plot_base(x2, overlay = list(x = c(-1.5, 1.5), y = c(-1.5, 1.5), lty = 1))

## Plot 3
theta <- seq(0, 2 * pi, length.out = 1000)
a <- 1.5; b <- 0.5; alpha <- 0.8
ex <- a * cos(theta) * cos(alpha) - b * sin(theta) * sin(alpha)
ey <- a * cos(theta) * sin(alpha) + b * sin(theta) * cos(alpha)
x3 <- rmvnorm(200, sigma = matrix(c(1, 0.75, 0.75, 1) * 0.22, 2))
plot_base(x3, overlay = list(x = ex, y = ey, lty = 3))

Replicating Figure 1 in the article.

The plots clarify the abstract theoretical foundation of dimensionality - whether public preferences best described as one-dimensional (aligned along a single ideological cleavage) or multidimensional (structured but only partially correlated). In the subsequent analysis, the authors establish a three-dimensional framework to capture the latent factors clustering individual political preferences.

3.3 Principal Component Analysis

To assess whether preferences are systematically organized and decide the optimal dimensionality, Pan and Xu (2018) apply principal component analysis (PCA) and exploratory factor analysis (EFA). PCA serves as a dimension-reduction tool and reveals whether variation in responses can be captured by a smaller set of composite components. It reduces the 50 survey items into a smaller set of uncorrelated principal components (PCs) that explain the most variance in responses. EFA, in addition, uncovers latent clusters without assuming a fixed structure. It analyzes the shared variance that load onto unobserved factors.

## China's Ideological Spectrum — PCA only
questions <- paste0("q", 1:50)

pca.out <- prcomp(d10K[, questions], center = TRUE, scale. = TRUE)

d10K <- mutate(d10K,
               PC1 = -pca.out$x[, 1],
               PC2 =  pca.out$x[, 2],
               PC3 =  pca.out$x[, 3])

## 3. Derive objects used later
d$PC1 <- -pca.out$x[, 1]          # reverse sign to match paper
d$PC2 <-  pca.out$x[, 2]
d$PC3 <-  pca.out$x[, 3]
ord   <- order(d$PC1)

RMatrix        <- pca.out$rotation
RMatrix[, 1]   <- -RMatrix[, 1]
for (i in 1:50) RMatrix[, i] <- RMatrix[, i] * sd(pca.out$x[, i])

## 4. Figure 4 (left) – scree plot of PCA vs EFA
tmp <- pdf(NULL)                      # open a null device (nothing is shown)
out <- psych::scree(d[, questions])   # still computes pcv and fv
fv  <- out$fv                         # factor eigen-values
pcv <- out$pcv                        # component eigen-values

# run summary(pca.out) to see the complete PCA results

The scree plot below compares the eigenvalues from PCA (black dots) and EFA (gray hollow dots).

par(mar = c(5, 4, 1, 1))
plot(1, type = "n", ylim = c(-0.5, 10), xlim = c(1, 50),
     xlab = "", ylab = "", axes = FALSE)
# Reduced text size from 1.5 to 1.2
mtext("i'th Principal Component or Factor", 1, 2.5, cex = 1.0)
mtext("Eigen-Values of Components and Factors", 2, 2.5, cex = 1.0)
abline(h = 1,               col = "#AAAAAA50", lwd = 4)
abline(h = seq(-1, 20, .5), col = "#AAAAAA50")
abline(v = seq(0, 50, 2),   col = "#AAAAAA50")
lines(fv, col = "gray30", lwd = 2, lty = 3)
points(fv,  pch = 16, col = "white", cex = 1.8)
lines(pcv, col = 1,       lwd = 2)
points(pcv, pch = 16, col = "white", cex = 1.8)
points(fv,  pch = 1, col = "gray30", cex = 1.1)
points(pcv, pch = 16, col = 1,       cex = 1.2)
axis(1, at = seq(0, 50, 10), cex.axis = 1.1)  
axis(2, at = 0:10,          cex.axis = 1.1)  
legend("topright",
       c("Principal Component", "Factor"),
       pch = c(16, 1), col = c(1, "gray30"),
       bty = "n", cex = 1.2)  # Reduced from 1.4
box()

Replicating Figure 4 (Left Panel) in the article.

The PCA results show that the first principal component explains approximately 18% of the variance, and the first three components together account for about 29%. Moreover, the first three factors in EFA have eigenvalues >1. Both sets similarly point to the three dominant latent dimensions.

To further evaluate constraint, the authors compute the percentage of responses correctly predicted (PCP).

choice  <- ifelse(d[, questions] >= 2.5, 1, 0)
s       <- scale(d[, questions])
R       <- pca.out$rotation
W       <- solve(R)
x       <- pca.out$x
s.mean  <- attr(s, "scaled:center")
s.sd    <- attr(s, "scaled:scale")

pred_fun <- function(i, accum = TRUE) {
  if (i == 0) {
    pred <- matrix(0, nrow(s), ncol(s))
  } else if (accum) {
    pred <- x[, 1:i, drop = FALSE] %*% W[1:i, , drop = FALSE]
  } else {
    pred <- x[,  i , drop = FALSE] %*% W[ i , , drop = FALSE]
  }
  sweep(pred, 2, s.sd, `*`) +
    matrix(s.mean, nrow(pred), 50, byrow = TRUE)
}

res1  <- sapply(0:50, function(i) mean(choice == (pred_fun(i)        >= 2.5), na.rm = TRUE))
res2  <- sapply(0:50, function(i) if (i == 0) mean(choice == 0, na.rm = TRUE)
                               else mean(choice == (pred_fun(i, FALSE) >= 2.5), na.rm = TRUE))
power <- (res2[-1] - res2[1]) * 100

ylim.power <- range(power, 0) + c(-1, 1)
lab.top <- c(0, 1, 3, 5, 10, 20, 30, 40, 50)
lab.bot <- c(1, 3, 5, 10, 20, 30, 40, 50)

# --- plotting ------------------------------------------------------------
par(mfrow = c(2, 1), mar = c(4.5, 5, 1, 1))

## (top) accumulative PCP
plot(0:50, res1 * 100, type = "n",
     xlab = "", ylab = "Percentage Correctly Predicted\n(Accumulative PCP) %",
     xlim = c(0, 50), ylim = c(58, 103), axes = FALSE)
abline(h = seq(60, 100, 5), v = c(1, 3, seq(0, 50, 2)), col = "#AAAAAA50")
lines(0:50, res1 * 100, lwd = 2)
points(0:50, res1 * 100, pch = 16, col = "white", cex = 1.8)
points(0:50, res1 * 100, pch = 16, cex = 1.2)
text(lab.top, res1[lab.top + 1] * 100, labels = round(res1[lab.top + 1] * 100, 1),
     pos = 3, cex = 0.8)
mtext("Number of Principal Components", 1, 2.5)
axis(1, at = c(1:3, seq(0, 50, 10))); axis(2); box()

## (bottom) independent predictive power
plot(1:50, power, type = "n",
     xlab = "", ylab = "Independent Predictive Power\n(Increase in PCP %)",
     xlim = c(0, 51), ylim = ylim.power, axes = FALSE)
abline(h = seq(floor(ylim.power[1]), ceiling(ylim.power[2]), 2),
       v = c(1, 3, seq(0, 50, 2)), col = "#AAAAAA50")
lines(1:50, power, lwd = 2)
points(1:50, power, pch = 16, col = "white", cex = 1.8)
points(1:50, power, pch = 16, cex = 1.2)
text(lab.bot, power[lab.bot], labels = round(power[lab.bot], 1),
     pos = 3, cex = 0.8)
abline(h = 0, col = "gray", lwd = 2)
mtext("i-th Principal Component", 1, 2.5)
axis(1, at = c(1:3, seq(0, 50, 10))); axis(2); box()

Replicating Figure 4 (Right Panel) in the article.

PC1 increases prediction accuracy from a baseline of 61% to 69%. Adding PC2 and PC3 further raises it to 73%. While preferences in China are less tightly structured than in consolidated democracies, there is still moderate ideological coherence across issue areas.

3.4 Configuration of Preferences

Next, the authors apply CFA to test 877 candidate models that map survey items to latent ideological dimensions. These models assume seven categories as classified by the Zuobiao Survey may map onto up to three latent dimensions and use diagonally weighted least squares for ordinal data. They are evaluated based on standard fit statistics (χ², root mean square error of approximation(RMSEA), and comparative fit index (CFI). The code below replicates the CFA results by dimensionality.

post.cov     <- which(results.final$post.check == 1)
good.results <- results.final[post.cov, ]
best.row     <- good.results[which.min(good.results$chisq), ]

## best model for each dimensionality (3 → 1)
best.by.ndim <- good.results %>%
  group_by(ndim) %>%
  slice_min(chisq, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  arrange(desc(ndim)) %>%
  mutate(Model = c("Model A", "Model B", "Model C"))

## Δχ²  and p-value versus the best (Model A)
best.chisq <- best.by.ndim$chisq[1]
best.ndim  <- best.by.ndim$ndim[1]

tbl <- best.by.ndim %>%
  mutate(`No. of Dimensions` = ndim,
         χ2   = chisq,
         CFI  = cfi,
         TLI  = tli,
         RMSEA = rmsea,
         `Δχ2`   = chisq - best.chisq,
         df     = best.ndim - ndim,
         `p-Value` = ifelse(df > 0,
                            1 - pchisq(`Δχ2`, df),
                            NA_real_)) %>%
  select(Model, `No. of Dimensions`, χ2, CFI, TLI, RMSEA, `Δχ2`, `p-Value`) %>%
  
  ## cosmetic formatting 
mutate(across(c(CFI, TLI, RMSEA),
              ~ sub("^0", ".", sprintf("%.3f", .x))),
       across(c(χ2, `Δχ2`),
              ~ formatC(.x, format = "d", big.mark = ",")),
       `p-Value` = ifelse(is.na(`p-Value`), "",
                          sprintf("%.3f", `p-Value`)))

knitr::kable(
  tbl,
  booktabs = TRUE,
  align = c("l", "c", rep("c", 6))
)
Model No. of Dimensions χ2 CFI TLI RMSEA Δχ2 p-Value
Model A 3 65760.80 0.9087920 0.9046674 0.0742398 0.0000
Model B 2 66061.75 0.9083698 0.9043893 0.0743480 300.9493 0.000
Model C 1 67941.37 0.9057170 0.9017049 0.0753844 2180.5679 0.000

Replicating Table 1 in the article.

Model A (three dimensions) fits the data best: lowest χ², highest CFI and TLI, lowest RMSEA. The χ² differences (Δχ²) between models are large and highly significant (p < 0.001).

Similarly, Pan and Xu (2018) visualize the goodness-of-fit statistics for all 877 candidate CFA models with different numbers of dimensions (1 to 7). The left plots the χ², and the right plot shows RMSEA.

# Figure 5: χ² and RMSEA
par(mfrow = c(1, 2), mar = c(4, 4, 1, 1))

plot(jitter(results.final$ndim[-post.cov]), results.final$chisq[-post.cov],
     xlim = c(0.5, 7.5), ylim = c(64500, 68100),
     col = "gray80", xlab = "# Latent Factors", ylab = "Chi-square")
points(jitter(results.final$ndim[setdiff(post.cov, best.row$id)]),
       results.final$chisq[setdiff(post.cov, best.row$id)],
       pch = 1, col = "gray30")
points(best.row$ndim, best.row$chisq, pch = 16, cex = 1.5)

plot(jitter(results.final$ndim[-post.cov]), results.final$rmsea[-post.cov],
     xlim = c(0.5, 7.5), ylim = c(0.0738, 0.0756),
     col = "gray80", xlab = "# Latent Factors", ylab = "RMSEA")
points(jitter(results.final$ndim[setdiff(post.cov, best.row$id)]),
       results.final$rmsea[setdiff(post.cov, best.row$id)],
       pch = 1, col = "gray30")
points(best.row$ndim, best.row$rmsea, pch = 16, cex = 1.5)

Replicating Figure 5 in the article.

Based on further examination of CFA results, the authors identify three latent dimensions: (i) political liberalism vs. conservatism, (ii) pro-market vs. anti-market/traditionalism, and (iii) nationalism vs. non-nationalism. For a detailed discussion of how these substantive dimensions were distilled, see the article’s section Substantive Meaning of Latent Traits.

## variables of interest and basic cleaning
vars      <- c("lt1", "lt2", "lt3", "PC1")
d_clean   <- na.omit(d[, vars])          # drop rows with any NA
d_clean[] <- lapply(d_clean, as.numeric) # ensure numeric columns

## pair plot with adjustments to prevent overlap
p <- ggpairs(
        d_clean,
        columnLabels = c("Political Liberalism",
                         "Promarket /\nNon-traditional Values",
                         "Nationalism (× -1)",
                         "1st Principal\nComponent"),  # Added line break
        lower = list(continuous = wrap("points", alpha = 0.3, size = 0.2),
                     combo      = wrap("dot",    alpha = 0.4, size = 0.2)),
        upper = list(continuous = wrap("cor", 
                                      size  = 5,
                                      colour = "black",
                                      fontface = "bold")),
        switch = "both"  # Moves y-axis labels to left side
     ) +
     theme(
       axis.text.x = element_text(size = 8, colour = "black", angle = 45, hjust = 1),  # Angled x-axis labels
       axis.text.y = element_text(size = 8, colour = "black"),
       axis.title = element_text(size = 10, face = "bold"),
       strip.text = element_text(size = 10, face = "bold", margin = margin(0,0,5,0)),  # Adjusted panel label spacing
       panel.spacing = unit(0.5, "lines")  # Added space between panels
     )

# Add more space at bottom for x-axis labels
p <- p + theme(plot.margin = margin(10, 10, 20, 10))

# Display the plot
p

Replicating Figure 9 in the article.

Political liberalism, pro-market/non-traditional values, and non-nationalism tend to co-occur, while political conservatism aligns with state intervention, traditionalism, and nationalism. These three strongly correlated axes define China’s ideological spectrum as a compact three-dimensional ovoid.

3.5 Correlates of Ideological Positions

After mapping the structure of ideology, Pan and Xu (2018) explored the correlates of ideological positions, including individual traits (education and age) and social factors (provincial features such as income per capita, trade openness, and level of urbanization). In addition to the reweighted Zuobiao data, they also use the ABS, a nationally representative, probability-based survey with a careful sampling design, for complementary robustness checks.

The authors first examine how individuals’ ideological orientations vary across education and income groups.

## plotting settings
colsU  <- c(1, "gray50", "gray80")          # upper series (3 traits)
pchU   <- c(16, 17, 15)
offU   <- seq(-0.15, 0.15, length.out = 3)

colsL  <- c(1, "gray50")                    # lower series (2 traits)
pchL   <- c(16, 17)
offL   <- c(-0.10, 0.10)

layout(matrix(c(1, 2, 3, 4), 2, 2, byrow = TRUE))
par(oma = c(4, 4, 4, 2), mar = c(4.5, 4.5, 2, 2))

### 1 ▸ Education – Zuobiao (upper-left) 
output <- ses.edu
plot(1, type = "n", xlim = c(0.5, 3.5), ylim = c(-0.6, 0.6),
     xlab = "Education", ylab = "", xaxt = "n",
     cex.lab = 1.5, cex.axis = 1.5)
axis(1, at = 1:3, labels = c("Below\nCollege", "College\n", "Above\nCollege"),
     tick = FALSE, line = 1, cex.axis = 1.2)
abline(h = 0); grid(col = "#AAAAAA50", lwd = 1)
for (k in 1:3) {
  for (i in 1:nrow(output))
    lines(rep(i, 2) + offU[k], output[i, k, 3:4], lwd = 4, col = colsU[k])
  points((1:nrow(output)) + offU[k], output[, k, 1],
         pch = pchU[k], cex = 2, col = colsU[k])
}
legend("topleft",
       legend = c("Political Liberalism",
                  "Promarket/Nontraditional Values",
                  "Nationalism × (–1)"),
       col = colsU, pch = pchU, lty = 1, lwd = 3, bty = "n", cex = 1.1,
       y.intersp = 1.2)

### 2 ▸ Income – Zuobiao (upper-right) 
output <- ses.income
plot(1, type = "n", xlim = c(0.5, 4.5), ylim = c(-0.6, 0.6),
     xlab = "Annual Income", ylab = "", xaxt = "n",
     cex.lab = 1.5, cex.axis = 1.5)
axis(1, at = 1:4, labels = c("0–50K", "50–150K", "150–300K", ">300K"),
     tick = FALSE, cex.axis = 1.2)
abline(h = 0); grid(col = "#AAAAAA50", lwd = 1)
for (k in 1:3) {
  for (i in 1:nrow(output))
    lines(rep(i, 2) + offU[k], output[i, k, 3:4], lwd = 4, col = colsU[k])
  points((1:nrow(output)) + offU[k], output[, k, 1],
         pch = pchU[k], cex = 2, col = colsU[k])
}

# Add Zuobiao label above first row
mtext("A  Zuobiao Sample", side = 3, outer = TRUE, line = 1, adj = 0.5, cex = 1.2)

### 3 ▸ Education – ABS (lower-left) 
output <- out.edu
plot(1, type = "n", xlim = c(0.5, 5.5), ylim = c(-1, 1),
     xlab = "", ylab = "", xaxt = "n",
     cex.lab = 1.5, cex.axis = 1.5)
axis(1, at = c(1, 3, 5),
     labels = c("< Primary\nSchool", "Middle School", "College or Above"),
     tick = FALSE, cex.axis = 1.1)
axis(1, at = c(2, 4), labels = c("Primary School", "High School"),
     tick = FALSE, line = 1.5, cex.axis = 1.1)
abline(h = 0); grid(col = "#AAAAAA80", lwd = 1)
for (k in 1:2) {
  for (i in 1:nrow(output))
    lines(rep(i, 2) + offL[k], output[i, k, 2:3], lwd = 4, col = colsL[k])
  points((1:nrow(output)) + offL[k], output[, k, 1],
         pch = pchL[k], cex = 2, col = colsL[k])
}
legend("topleft",
       legend = c("Political Liberalism", "Nontraditional Values"),
       col = colsL, pch = pchL, lty = 1, lwd = 3, bty = "n", cex = 1.1,
       y.intersp = 1.2)
box()

### 4 ▸ Income – ABS (lower-right) 
output <- out.inc
plot(1, type = "n", xlim = c(0.5, 5.5), ylim = c(-1, 1),
     xlab = "Self-Reported Income Level", ylab = "", xaxt = "n",
     cex.lab = 1.5, cex.axis = 1.5)
axis(1, at = 1:5,
     labels = c("Lowest", "Low", "Middle", "High", "Highest"),
     tick = FALSE, cex.axis = 1.2)
abline(h = 0); grid(col = "#AAAAAA80", lwd = 1)
for (k in 1:2) {
  for (i in 1:nrow(output))
    lines(rep(i, 2) + offL[k], output[i, k, 2:3], lwd = 4, col = colsL[k])
  points((1:nrow(output)) + offL[k], output[, k, 1],
         pch = pchL[k], cex = 2, col = colsL[k])
}
box()

# Add ABS label below second row
mtext("B  ABS Sample", side = 1, outer = TRUE, line = 2, adj = 0.5, cex = 1.2)

Replicating Figure 10 in the article.

Higher levels of education and income are consistently associated with greater support for liberalism, market-oriented reforms, and nonnationalist attitudes.

Figure 11 measures mean ideological orientation by age group.

## settings
pchA <- c(16, 17, 15)      # Zuobiao panels
pchB <- 16                 # ABS panels
leg  <- c("Political Liberalism",
          "Promarket/Nontraditional Values",
          "Nationalism × (–1)")
layout(matrix(c(1, 2, 3, 4, 5, 0), 2, 3, byrow = TRUE))
par(oma = c(4, 4, 2, 2), mar = c(4.5, 4.5, 2, 2))

### Top row – Zuobiao (3 traits) 
outZ <- ses.age
for (k in 1:3) {
  plot(1, type = "n", xlim = c(-1, 45), ylim = c(-0.6, 0.6),
       xlab = "Age", ylab = "", xaxt = "n",
       cex.lab = 1.5, cex.axis = 1.5)
  axis(1, at = seq(1, 43, 3), labels = seq(18, 60, 3), cex.axis = 1.2)
  abline(h = 0); grid(col = "#AAAAAA50", lwd = 1)
  for (i in 1:nrow(outZ))
    lines(rep(i, 2), outZ[i, k, 3:4], lwd = 2)  # Thinner lines (was 4)
  points(1:nrow(outZ), outZ[, k, 1], pch = pchA[k], cex = 1.5)  # Smaller dots (was 2)
  title(leg[k], line = -2, cex.main = 1.5)
}

### Bottom row – ABS (2 traits) 
outA <- out.age
for (k in 1:2) {
  plot(1, type = "n", xlim = c(0.5, 43.5), ylim = c(-1, 1.5),
       xlab = "Age", ylab = "", xaxt = "n",
       cex.lab = 1.5, cex.axis = 1.5)
  axis(1, at = seq(1, 43, 3), labels = seq(18, 60, 3), cex.axis = 1.2)
  abline(h = 0); grid(col = "#AAAAAA50", lwd = 1)
  for (i in 1:nrow(outA))
    lines(rep(i, 2), outA[i, k, 2:3], lwd = 2)  # Thinner lines (was 4)
  points(1:nrow(outA), outA[, k, 1], pch = pchB, cex = 1.5)  # Smaller dots (was 2)
  title(c("Political Liberalism", "Nontraditional Values")[k],
        line = -2, cex.main = 1.5)
}

## row labels
mtext("A  Zuobiao Sample", side = 3, outer = TRUE, line = 0, adj = 0.5, cex = 1.2)
mtext("B  ABS Sample", side = 1, outer = TRUE, line = 1, adj = 0.5, cex = 1.2)

Replicating Figure 11 in the article.

Among individuals under 35, politically liberal and promarket orientations are more common. After age 35, politically conservatism and antimarket/traditionalism increase with age. With ABS data, the plot below reveals a sharper age-based divergence in rural areas.

mycol = c(1, "gray50")
mypch = c(16, 17)
offset <- c(-0.1,0.1)

## Set up 2x2 layout with smaller margins
par(mfrow=c(2,2), mar=c(4.5,4.5,3,2), oma=c(0,0,2,0))

### Urban Political Liberalism
output <- out.age.urban
plot(1, type="n", ylab="Estimate", 
     xlab="Age", ylim=c(-1,1.5), xlim=c(0.5,43.5),
     cex.lab=1.3, cex.axis=1.3, xaxt="n", main="Urban: Political Liberalism")
abline(h=0)
axis(1, at=seq(1,43,by=3), lab=seq(18,60,by=3), cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1)
n <- dim(output)[1]
for (k in 1) {
    for (i in 1:n) {
        lines(c(i,i) + offset[k], c(output[i,k,2],output[i,k,3]), lwd=3, col=1)
    }
    points(c(1:n) + offset[k], output[,k,1], pch=mypch[k], cex=1.5, col=1)
}
box()

### Urban Non-Traditional Values
plot(1, type="n", ylab="Estimate",
     xlab="Age", ylim=c(-1,1.5), xlim=c(0.5,43.5),
     cex.lab=1.3, cex.axis=1.3, xaxt="n", main="Urban: Non-Traditional Values")
abline(h=0)
axis(1, at=seq(1,43,by=3), lab=seq(18,60,by=3), cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1)
for (k in 2) {
    for (i in 1:n) {
        lines(c(i,i) + offset[k], c(output[i,k,2],output[i,k,3]), lwd=3, col=1)
    }
    points(c(1:n) + offset[k], output[,k,1], pch=mypch[k], cex=1.5, col=1)
}
box()

### Rural Political Liberalism
output <- out.age.rural
plot(1, type="n", ylab="Estimate",
     xlab="Age", ylim=c(-1,1.5), xlim=c(0.5,43.5),
     cex.lab=1.3, cex.axis=1.3, xaxt="n", main="Rural: Political Liberalism")
abline(h=0)
axis(1, at=seq(1,43,by=3), lab=seq(18,60,by=3), cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1)
n <- dim(output)[1]
for (k in 1) {
    for (i in 1:n) {
        lines(c(i,i) + offset[k], c(output[i,k,2],output[i,k,3]), lwd=3, col=1)
    }
    points(c(1:n) + offset[k], output[,k,1], pch=mypch[k], cex=1.5, col=1)
}
box()

###. Rural Non-Traditional Values
plot(1, type="n", ylab="Estimate",
     xlab="Age", ylim=c(-1,1.5), xlim=c(0.5,43.5),
     cex.lab=1.3, cex.axis=1.3, xaxt="n", main="Rural: Non-Traditional Values")
abline(h=0)
axis(1, at=seq(1,43,by=3), lab=seq(18,60,by=3), cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1)
for (k in 2) {
    for (i in 1:n) {
        lines(c(i,i) + offset[k], c(output[i,k,2],output[i,k,3]), lwd=3, col=1)
    }
    points(c(1:n) + offset[k], output[,k,1], pch=mypch[k], cex=1.5, col=1)
}
box()

# Add overall title
mtext("Age Trends in Political and Cultural Values by Urban/Rural Residence", 
      outer=TRUE, cex=1.5, line=0)

Replicating Figure A5 in the appendix.

Beyond generational change and life-cycle dynamics, Pan and Xu (2018) test the correlations between provincial-level economic development on ideology. For Zuobiao respondents, Figure 12 plots the average political scores against three provincial economic factors: log income per capita, trade openness, and urbanization.

lt.prov <- ddply(d, .(provgb), function(x){
    data.frame(lt1 = mean(x$lt1),
               lt2 = mean(x$lt2),
               lt3 = mean(x$lt3))
})

prov <- join(prov.corr, lt.prov, by='provgb', type='left', match='all')
prov <- subset(prov, !provgb %in% c(54, 63, 64))

## Set up 1x3 layout
par(mfrow=c(1,3), mar=c(4.5,4.5,2,1), oma=c(0,0,2,0))

### Income plot
plot(1, type="n", ylim=c(-0.4,0.4), xlim=c(9.75,10.75),
     xlab="Log Income per capita", ylab="Political Liberalism",
     cex.lab=1.3, cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1.5)
points(log(prov$income), prov$lt1, pch=16, cex=2, col="gray80")
lines(lowess(log(prov$income), prov$lt1, f=1), col=1, lwd=3)

### Trade plot
plot(1, type="n", ylim=c(-0.4,0.4), xlim=c(-5,155),
     xlab="Trade openness", ylab="",
     cex.lab=1.3, cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1.5)
points(prov$openness, prov$lt1, pch=16, cex=2, col="gray80")
lines(lowess(prov$openness, prov$lt1, f=1), col=1, lwd=3)
text(145,0.10,"Beijing",cex=1.1)
text(135,0.17,"Shanghai",cex=1.1)
text(115,0.21,"Guangdong",cex=1.1)

### Urbanization plot
plot(1, type="n", ylim=c(-0.4,0.4), xlim=c(25,92),
     xlab="Urbanization (%)", ylab="",
     cex.lab=1.3, cex.axis=1.3)
grid(col="#AAAAAA50", lwd=1.5)
points(prov$urban, prov$lt1, pch=16, cex=2, col="gray80")
lines(lowess(prov$urban, prov$lt1, f=1), col=1, lwd=3)

# Add overall title
mtext("Provincial Correlates of Political Liberalism", outer=TRUE, cex=1.5)

Replicating Figure 12 in the appendix.

The subplots each reveal a positive association: provinces that are wealthier, more urbanized, and more globally integrated tend to exhibit more liberal ideological profiles. This pattern, combined with Figure 5A above, implies that economic modernization is linked to ideological liberalization in China.

4 Summary

Pan and Xu (2018) find that Chinese public preferences are structured along a three-dimensional ideological spectrum encompassing: 1) political liberalism vs. political conservatism, 2) pro-market and Western social values vs. anti-market and traditional social values, and 3) nationalism. Although ideological constraints among the public are weaker in China than in competitive democracies, policy preferences of the public appear to be organized.

Reference

Barberá, Pablo. 2015. “Birds of the Same Feather Tweet Together. Bayesian Ideal Point Estimation Using Twitter Data.” Political Analysis 23: 76–91.
Bonica, Adam. 2014. “Mapping the Ideological Marketplace.” American Journal of Political Science 58 (2): 367–86.
Converse, Philip E. 1964. “The Nature of Belief Systems in Mass Publics.” In Ideology and Discontent, edited by David E. Apter. New York: The Free Press of Glencoe.
Groseclose, Tim, and Jeffrey Milyo. 2005. “A Measure of Media Bias.” The Quarterly Journal of Economics 120 (4): 1191–1237.
Pan, Jennifer, and Yiqing Xu. 2018. China’s Ideological Spectrum.” The Journal of Politics 80 (1): 254–73.