Much attention is now being given to the construction of models of urban structure of a type which are statistical and computerised and which are contributing the first wave of generalised urban models since those produced by the American school of human ecology in the 1920’s and 1930’s. This new interest in model-making is however, paralleled by a series of attempts at formulating new methods of analysis for the study of urban structure. These again have their analogies in the interwar Chicago school, where the production of the general ecological models was preceded by extensive social investigation and the mapping of social data.
Combining the scholarship on spatial structure and segregation, urban scholars have long recognized the value of studying multi-dimensional segregation, i.e. how cities partition into smaller communities along the lines of race, ethnicity, socioeconomic status, family structure, etc. Work in this tradition develops and applies methods to identify patterns like Burgess’s concentric zones model, and asks (a) which “dimensions” are most important for understanding residential sorting, and (b) whether cities in different regions, countries, or political/economic systems follow similar patterns.
Before the Geography of Opportunity (Galster & Killen, 1995), there was the Ecology of Inequality (Massey & Eggers, 1990), and prior to that, a massive literature on ‘factor ecology’ which adopts exploratory factor analysis to analyze residential differentiation(Rees, 1971). An important vestige of this tradition is the focus on neighborhood differentiation as an outcome of social processes. Thus, the goal is to collect neighborhood data and conduct factor analysis to understand which neighborhood variables seem to measure the same “underlying construct”. That is, while the ultimate result is a dimensionality-reduction technique, the ‘factors’ uncovered by the researchers are viewed as [partial] measurements of different segregating processes.
In this section we explore comparative factor ecology (and extend it) using the canonical examples of Los Angeles and Chicago. These two cities (metropolitan regions, actually) serve as a useful comparison because they are two large and well known cities, but also because the conceptual research design and empirical examples were developed in these places (Shevky & Williams, 1949). Further, some have argued that L.A. and Chicago represent two distinct traditions of urban research, both focused on community scholarship albeit with different lineage (Dear, 2002).
The original idea concept behind social area analysis and factorial ecology is to summarize urban data along its primary axes, then classify areas according to these axes. Although both SAA and FE drew considerable criticism for being atheoretical, the formalization of the method was intended directly to address several hypotheses about social and spatial structure (Arsdol et al., 1958; Bell, 1955; Bell & Greer, 1962; Schmid et al., 1958; Van Arsdol et al., 1961, 1962). This predates the inception of confirmatory factor analysis, so the hypothesis testing was less stringent, but the hypotheses were explicit nonetheless.
The first testable hypothesis is that American cities divide themselves along three principal axes related to economic status, family status, and ethnic status, which together provide the foundation for location choice and multidimensional segregation (Bell, 1955). The second set of hypotheses focus on the relationship between the revealed dimensions and social behaviors for populations in different areas. This is an early forerunner to neighborhood effects research (Green, 1971; Greer, 1960; Johnston et al., 2004).
Shevky, Williams, and Bell [301, 3022] argued that most of the social differentiation and stratification of the population in the United States can be summarized in three primary social “dimensions”:
an index of social rank measuring socioeconomic status,
an index of urbanization measuring family status,
and an index of segregation measuring ethnic status
And as decribed above, these measures of social differentiation were viewed as outcomes of unobservable social processes (i.e. segregation by age and family size). Following, scholars used these variables to test other hypotheses, such as whether having a larger family resulted in different community-level behaviors like voting turnout or civic participation (Greer, 1956; Morgan, 1984).
Factorial ecology and social area analysis endured a great deal of criticism before being essentially abandoned by the 1990s, however these two hypotheses–especially the first–are probably among the most replicated findings in empirical urban research. At its core, factor ecology is fundamentally an exploratory method that assumes residential sorting in the urban system follows a number of empirical patterns; following, “accepting the systemic assumption, factorial ecology asks the question ‘how does the system cohere and pattern?’ The answer is sought by trying to identify repetitive sequences of spatial variation present in many observable attributes of area” (Berry, 1971). In other words, given a variety of data in spatial form, our goal is to uncover and compare the primary axes of urban differentiation. These factors measure the primary dimensions of residential differentiation and are powerful descriptors of the regions under study, but can also be used to generate further hypotheses and test other behavioral differentiation as described above.
20.1 Exploratory Factor Analysis
For an overview of the factorial ecology method, see Rees (1971). Methodologically, factorial ecology relies on exploratory factor analysis (EFA), a decomposition technique designed to recover latent processes that lead to the observed variables, and is used heavily in psychological personality theory Velicer & Jackson (1990). For this purpose, we will again rely on the factor_analyzer package, and will begin by collecting data for the Los Angeles and Chicago metropolitan regions (blockgroup level for ACS 2017-2021). Since we need to perform the same handful of operations to two different datasets, its easiest to define a quick function that handles both. The original factor ecology work also includes an isolation segregation measure, so we will adopt the recent local distortion index here.
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/io/util.py:273: UserWarning: Unable to find local adjustment year for 2021. Attempting from online data
warn(
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/io/constructors.py:218: UserWarning: Currency columns unavailable at this resolution; not adjusting for inflation
warn(
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/io/util.py:273: UserWarning: Unable to find local adjustment year for 2021. Attempting from online data
warn(
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/io/constructors.py:218: UserWarning: Currency columns unavailable at this resolution; not adjusting for inflation
warn(
20.1.1 Correlation Structure
As in Chapter 16, a useful visualization to begin a factor analysis with is a heatmap of the pairwise correlation matrix of our input variables, and groups of variables that form blocks along the main diagonal are likely to load onto the same factor. Since we have only 25 variables in our dataset, we can plot the heatmaps for both regions for a quick comparison. Rather than stick to the variables in the original factor ecology literature, we will just throw everything in our dataset at the factor analysis and see what comes back; our variables include a relatively small of socioeconomic and demographic measures like race, age, income, educational attainment, and marital status.
Code
sns.clustermap( chi_corr, cmap="RdBu_r", annot=True, fmt=".2f", figsize=(10, 10), annot_kws={"size": 6},)plt.suptitle("Correlation Structure in Chicago Region", fontsize=20)# plt.tight_layout()plt.show()
Heatmap of Correlation Structure in Chicago Region
Code
sns.clustermap( la_corr, cmap="RdBu_r", annot=True, fmt=".2f", figsize=(10, 10), annot_kws={"size": 6},)plt.suptitle("Correlation Structure in LA Region", fontsize=20)plt.show()
Heatmap of Correlation Structure in LA Region
A common way to explore the appropriate number of factors to extract is to create a so-called “scree plot”, which is a line plot that shows the amount of variance explained by the factor on the y-axis, and the number of factors on the x-axis. To determine the appropriate number of factors, we look for the ‘elbow’, where the plot kinks and begins to level horizontally. There is no function to create a scree plot in the packages at our disposal, but it is easy to create one by hand. First we fit a factor analysis on each dataset with n_factors equal to the number of variables in our dataset. Then, we plot the eigenvalues, which describe the total variance explained by each factor.
Code
# collinearla_corr = la_corr.drop( columns=["p_asian_indian_persons","p_vacant_housing_units","p_nonhisp_white_persons", ])chi_corr = chi_corr.drop( columns=["p_asian_indian_persons","p_vacant_housing_units","p_nonhisp_white_persons", ])cols = chi_corr.columns# create and fit factor analysis on z-standardized data# using as many factors as there are variablesfa_la = FactorAnalyzer(rotation="oblimax", n_factors=la_corr.shape[1])fa_la.fit(la[cols].apply(lambda x: zscore(x, ddof=1)))fa_chi = FactorAnalyzer(rotation="oblimax", n_factors=chi_corr.shape[1])fa_chi.fit(chi[cols].apply(lambda x: zscore(x, ddof=1)))# collect the factor measures and store then as pandas Seriesevla = fa_la.get_eigenvalues()[0]evla = pd.Series(evla, index=range(1, len(evla) +1))evla = evla / evla.sum()evchi = fa_chi.get_eigenvalues()[0]evchi = pd.Series(evchi, index=range(1, len(evchi) +1))evchi = evchi / evchi.sum()# scree plot for each regionf, ax = plt.subplots(1, 2, figsize=(4, 2), sharey=True)evla.iloc[:10].plot(grid=True, style=".-", ax=ax[0])ax[0].set_title("LA")ax[0].set_ylabel("% of Variance")evchi.iloc[:10].plot(grid=True, style=".-", ax=ax[1])ax[1].set_title("Chicago")
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/sklearn/utils/deprecation.py:132: FutureWarning: 'force_all_finite' was renamed to 'ensure_all_finite' in 1.6 and will be removed in 1.8.
warnings.warn(
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/sklearn/utils/deprecation.py:132: FutureWarning: 'force_all_finite' was renamed to 'ensure_all_finite' in 1.6 and will be removed in 1.8.
warnings.warn(
Text(0.5, 1.0, 'Chicago')
Scree Plots for Ecological Factors in Chicago and L.A.
In these data, the scree plots show a clear elbow at four factors in L.A., but five factors in Chicago. Since the original work focuses on three factors, we will fit four here in both cases.
20.1.2 Los Angeles
Knowing how many factors we want to extract, we can re-fit the model and examine the factor loadings, which describe how each input variable is related to each factor. In a good factor model, we want to represent “simple structure,” where (ideally) each variable loads strongly onto a single factor (Thurstone, 1954). One important choice for generating simple structure is the choice of factor rotation used by the analyst; rotations can either be orthogonal (meaning factors are forced to be uncorrelated) or oblique (meaning they can assume any position and my correlate with one another). A full treatment of rotation methods is beyond the scope of this text (though see chapters 10-12 of Mulaik (2009) for an extremely thorough discussion), however it is sufficient to say that oblique rotation is typically preferred today because strictly uncorrelated factors are unlikely for many conceptual problems.
For most work Direct Oblimin is to be recommended. Actually it is a good check to use one other method of rotation, since any one method can be caught out occasionally by particular configurations of data. In most instances, however, there will not be considerable differences. If orthogonal simple structure is preferred then all authorities agree that Varimax is the most effective procedure.
Loadings less than .1 are considered unimportant (R and others suppress them), and those less than 0.3 can generally be ignored (Kline, 2014; Revelle, n.d.). Here we will mask out any lodings lower than 0.3, as “it is usual to regard factor loadings as high if they are greater than 0.6 (the positive or negative sign is irrelevant) and moderately high if they are above 0.3. Other loadings can be ignored” (Kline, 2014, p. 6).
Code
fala = FactorAnalyzer( n_factors=4, rotation="oblimin",)fala.fit(la[cols].apply(lambda x: zscore(x, ddof=1)))# create a dataframe of the factor loadings for each regionfactors_la = pd.DataFrame.from_records( fala.loadings_, index=la_corr.columns, columns=["F1", "F2", "F3", "F4"])factors_la = factors_la.mask(abs(factors_la) <0.3)factors_la.dropna(how="all")
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/sklearn/utils/deprecation.py:132: FutureWarning: 'force_all_finite' was renamed to 'ensure_all_finite' in 1.6 and will be removed in 1.8.
warnings.warn(
F1
F2
F3
F4
median_home_value
0.793648
NaN
NaN
NaN
median_contract_rent
NaN
NaN
-0.411587
NaN
median_household_income
0.578341
NaN
NaN
0.459390
per_capita_income
0.890836
NaN
NaN
NaN
p_owner_occupied_units
-0.359370
NaN
NaN
0.312355
p_housing_units_multiunit_structures
NaN
NaN
NaN
-0.851415
p_persons_under_18
NaN
-0.439877
0.300706
NaN
p_persons_over_60
NaN
0.898522
NaN
NaN
p_persons_over_75
NaN
0.761145
NaN
NaN
p_married
NaN
NaN
NaN
0.648701
p_widowed_divorced
NaN
0.557405
NaN
NaN
p_female_headed_families
NaN
NaN
0.365621
NaN
p_hispanic_persons
NaN
NaN
0.619920
NaN
p_asian_persons
NaN
NaN
-0.591268
NaN
p_edu_hs_less
NaN
NaN
0.596763
NaN
p_edu_college_greater
0.551567
NaN
-0.513595
NaN
p_veterans
NaN
0.438774
NaN
NaN
pop_density
NaN
NaN
NaN
-0.446659
distortion_seg
NaN
NaN
0.583896
NaN
In Greater Los Angeles, the first factor loads strongly onto income, home values, and college education, which corresponds to the conventional socioeconomic status dimension. The second factor in L.A. loads strongly onto age, and to a lesser extent veterancy and divorced marital status. This factor largely captures some dimension of family status, presumably capturing the way older households settle into established neighborhoods. The third dimension clearly measures segregation, and loads onto the Hispanic/Latino and Asian population (positively for the former and negatively for the latter), as well as the distortion segregation measure; lower educational attainment (less than high school) also loads positively onto this factor, which may capture sorting or differential attainment among groups. Finally, the fourth factor loads onto multi-unit housing, and to a lesser extent crude population density and households with married householders. We could probably safely call this factor ‘urbanization’.
These results are very similar to Anderson & Bean (1961), who find that the original Shevky/Bell urbanization factor is better split into two concepts, one demographic and one morphological (Anderson & Bean, 1961). As a generic categorization, these factors map fairly well onto the original SAA factors (as long as you have them in mind), though obviously the loading structure can be quite different across cities, even if the conceptual factors are similar.
As an alternative to the loadings table, it can be useful to visualize the variable/factor relationships using a network diagram (for which there is standard tooling in R). In Python, there is no simple function built into the factor analysis packages (yet), though it is straghtforward to create a simple diagram using networkX and graphviz (the latter of which is not technically necessary, though helps dramatically with the layout). To do this, we stack our loading matrix to create an adjacency list representation, then use it to create a new networkX graph object. Then, we draw the graph (which has nodes for each variable and factor, then draws the edges using the loading matrix). We will also use a cool/warm colormap so factors that load positively get a darker hue of red and those loading negatively get progressively more blue.
Code
Gla = nx.from_pandas_edgelist( factors_la.T.stack().rename("weight").reset_index().round(3), source="level_0", target="level_1", edge_attr="weight", edge_key="weight", create_using=nx.DiGraph,)f, ax = plt.subplots(figsize=(8, 11))pos = graphviz_layout(Gla, prog="dot", args='-Grankdir="LR"')nx.draw_networkx( Gla, pos=pos, with_labels=True, ax=ax, edge_cmap=plt.cm.RdBu_r, edge_color=factors_la.T.stack().values, node_size=500, width=3, arrowsize=14,)labels = nx.get_edge_attributes(Gla, "weight")nx.draw_networkx_edge_labels(Gla, pos, edge_labels=labels)ax.margins(0.2, None) # add some horizontal space to fit labelsax.axis("off")plt.suptitle("Factor Loadings in LA", fontsize=18)plt.tight_layout()plt.show()
Factor Loadings in Los Angeles
The network visualization helps demonstrate that this model has done a pretty good job of generating the ‘simple structure’ we are shooting for; there are only a couple cross-loading variables, and most load strongly onto a single factor, giving us relatively distinct dimensions.
Unlike the dimensions of segregation explored in Chapter 16, these latent variables are more useful than their constituent parts. Thus we can use the factor model to estimate the latent variables for each observation to map or analyze them further. To do so, let us first rename each factor for easier presentation (which we do by mapping names via a dictionary), then use the transform method to generate our factor scores, once for each column. We will also switch the sign of the urbanization factor for ease of interpretation, since higher factor values correspond to greater population density, etc. Finally, we will plot each factor as a choropleth using a quintile classification.
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/sklearn/utils/deprecation.py:132: FutureWarning: 'force_all_finite' was renamed to 'ensure_all_finite' in 1.6 and will be removed in 1.8.
warnings.warn(
Map of Social Ecological Factors in L.A.
These maps show us the spatial pattern of the four ‘social ecological factors’ uncovered in the Los Angeles metro region, and each reveals some distinct patterning, especially in the segregation and SES factors, which seem to show an urban/suburban divide.