CertSafari
    Databricks Certified Machine Learning Associate· Lessons

    Domain 2 · Lesson 22/48

    Comparing Two Features in Spark: Pearson Correlation vs Crosstab

    Compare two categorical or two continuous features using the appropriate method

    14 min read
    2.08% of exam
    7 sources
    Published 2 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Pick a comparison method from the types of the two features: Pearson correlation for two continuous features, a contingency table for two categorical features
    • Compute Pearson correlation with df.corr, df.stat.corr, sf.corr or the SQL corr aggregate, and say what the method, DISTINCT and FILTER options do
    • Explain why covariance (cov) and correlation (corr) return different numbers for the same pair of columns
    • Build a contingency table with crosstab and read its $col1_$col2 header and its zero counts

    Key concept

    Match the method to the feature type — The comparison method depends on what the two features are. Two continuous (numeric) features are summarised by one Pearson correlation coefficient. Two categorical features are compared by counting how often each pair of values occurs together, which gives a contingency table.

    1.Start with the feature types

    Before you compute anything, look at the types of the two columns. If both are continuous, such as price and square footage, you can reduce their relationship to one number: a correlation coefficient. If both are categorical, such as payment method and region, you can't do arithmetic on the labels. Instead you count how often each combination of values appears, and that grid of counts is called a contingency table.

    In PySpark, the tools for both cases are on one object, DataFrameStatFunctions, which the docs describe as "Functionality for statistical functions with a DataFrame." You reach it through df.stat. The table below lists the three methods that matter for comparing two features. The same class has other methods, such as quantiles, frequent items and stratified sampling, but they belong to other tasks.

    The DataFrameStatFunctions methods that compare two columns
    MethodFeature pair it suitsWhat it returns
    corr(col1, col2, method)Two continuous featuresPearson correlation coefficient as a double value
    cov(col1, col2)Two continuous featuresSample covariance as a double value
    crosstab(col1, col2)Two categorical featuresPair-wise frequency table (a DataFrame)

    Checkpoint 1 of 8· Check yourself

    Which pairing of feature types and Spark method is correct?

    Sources12

    2.Pearson correlation from a DataFrame

    For two continuous columns, call corr with the two column names and you get back one float. DataFrame.corr and df.stat.corr are not two implementations that might disagree. The docs say plainly that "DataFrame.corr and DataFrameStatFunctions.corr are aliases of each other." Use whichever form reads better.

    df.corr on two small DataFrames: an inverse relationship and a perfect positive onepython
    df = spark.createDataFrame([(1, 12), (10, 1), (19, 8)], ["c1", "c2"])
    df.corr("c1", "c2")
    # -0.3592106040535498
    df = spark.createDataFrame([(11, 12), (10, 11), (9, 10)], ["small", "bigger"])
    df.corr("small", "bigger")
    # 1.0

    Look at the second example. In every row, bigger is exactly one more than small, so the two columns move in perfect step and the result is 1.0. The first example mixes directions and gives a negative value.

    The signature has a third argument, method, which looks like it might let you choose another kind of correlation. It doesn't. The parameter table says: "The correlation method. Currently only supports \"pearson\"." If an exam option passes a different method name to this API, that option is wrong.

    If you want the correlation as a column expression instead of a value returned to the driver, use the corr function in pyspark.sql.functions. It "Returns a new Column for the Pearson Correlation Coefficient for col1 and col2", so you use it inside agg:

    sf.corr used as an aggregate column; b is always 2 × a, so the result is 1.0python
    from pyspark.sql import functions as sf
    a = range(20)
    b = [2 * x for x in range(20)]
    df = spark.createDataFrame(zip(a, b), ["a", "b"])
    df.agg(sf.corr("a", df.b)).show()
    
    +----------+
    |corr(a, b)|
    +----------+
    |       1.0|
    +----------+

    Checkpoint 2 of 8· Fill the gap

    This call returned -0.3592106040535498 for two numeric columns. Which method fills the blank?

    df = spark.createDataFrame([(1, 12), (10, 1), (19, 8)], ["c1", "c2"])
    df. ? ("c1", "c2")
    # -0.3592106040535498

    Checkpoint 3 of 8· Exam question

    A data science team at a retail company has a Spark DataFrame with two categorical columns, `customer_segment` (Budget, Value, Premium, Luxury) and `preferred_channel` (Online, In-Store, Mobile App). They want to determine whether a statistically significant association exists between which segment a customer belongs to and which channel they prefer. Which approach should they use?

    Sources34

    3.The SQL corr aggregate: DISTINCT and FILTER

    In Databricks SQL and Databricks Runtime, corr is an aggregate function. Both arguments must be numeric: "expr1: An expression that evaluates to a numeric." The same applies to expr2. The result is a DOUBLE. It accepts two optional modifiers, and both change which rows go into the calculation.

    The same four rows give three different coefficients depending on DISTINCT and FILTERsql
    > SELECT corr(c1, c2) FROM VALUES (3, 2), (3, 3), (3, 3), (6, 4) as tab(c1, c2);
     0.816496580927726
    
    > SELECT corr(DISTINCT c1, c2) FROM VALUES (3, 2), (3, 3), (3, 3), (6, 4) as tab(c1, c2);
     0.8660254037844387
    
    > SELECT corr(DISTINCT c1, c2) FILTER(WHERE c1 != c2)
        FROM VALUES (3, 2), (3, 3), (3, 3), (6, 4) as tab(c1, c2);
     1.0

    The third query adds FILTER (WHERE c1 != c2). That drops the (3, 3) pair completely and leaves (3, 2) and (6, 4). Those two surviving points slope upward, and the documented result is 1.0. A perfect coefficient from a heavily filtered query tells you about the rows that survived the filter, not about the features as a whole. The docs also note the function "can also be invoked as a window function using the OVER clause", which lets you compute a correlation per partition instead of over the whole table.

    Checkpoint 4 of 8· Check yourself

    You want the SQL corr aggregate to ignore rows where a sensor reported a known-bad status, without writing a subquery. Which part of the syntax does this?

    Sources5

    4.Covariance is not the same number

    The stat functions include a second two-column numeric method, cov. It is easy to confuse with corr because the signature is the same and so is the return type, a double. What it computes is different: it will "Calculate the sample covariance for the given columns, specified by their names, as a double value." Run it on the same data as the first corr example and compare.

    cov on the same two DataFrames used for corr abovepython
    df = spark.createDataFrame([(1, 12), (10, 1), (19, 8)], ["c1", "c2"])
    df.cov("c1", "c2")
    # -18.0
    df = spark.createDataFrame([(11, 12), (10, 11), (9, 10)], ["small", "bigger"])
    df.cov("small", "bigger")
    # 1.0

    Checkpoint 5 of 8· Match them up

    Match each call to what it gives you

    Tap a term, then the definition that fits it.

    Checkpoint 6 of 8· Exam question

    A real estate analytics team has a Spark DataFrame containing two continuous numeric columns, `square_footage` and `sale_price`, for thousands of home sales. They want to quantify the strength and direction of the linear relationship between these two features. Which method is appropriate?

    Sources6

    5.Two categorical features: the contingency table

    When both features are categorical, you count co-occurrences instead of computing a coefficient. crosstab does this. The distinct values of the first column become the rows, the distinct values of the second column become the column headers, and each cell holds how many rows had that pair of values. As with corr, the DataFrame and stat forms are aliases: df.crosstab and df.stat.crosstab give the same result.

    crosstab on two columns; each cell counts how many rows had that (c1, c2) pairpython
    df = spark.createDataFrame([(1, 11), (1, 11), (3, 10), (4, 8), (4, 8)], ["c1", "c2"])
    df.crosstab("c1", "c2").sort("c1_c2").show()
    # +-----+---+---+---+
    # |c1_c2| 10| 11|  8|
    # +-----+---+---+---+
    # |    1|  0|  2|  0|
    # |    3|  1|  0|  0|
    # |    4|  0|  0|  2|
    # +-----+---+---+---+

    Three details matter when you read this output. First, the leading column is not called c1. The docs say "The name of the first column will be $col1_$col2", which is why the example sorts on "c1_c2". Second, combinations that never occur still get a cell: "Pairs that have no occurrences will have zero as their counts", so you see 0, not null. Third, the result is a DataFrame ("Frequency matrix of two columns"), not a single statistic, so you can sort it, filter it or display it like any other DataFrame.

    In this example, each value of c1 appears with only one value of c2, so every row of the table has a single non-zero cell.

    Checkpoint 7 of 8· Check yourself

    In the crosstab output above, what is in the cell for c1_c2 = 3 under column 11?

    Checkpoint 8 of 8· Exam question

    A machine learning engineer is working with a native PySpark DataFrame `df` that has two numeric columns, `engine_size` and `fuel_consumption`, and wants to compute the Pearson correlation coefficient between them without converting to pandas. Which method call accomplishes this?

    Sources27

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.The method argument of DataFrame.corr lets you switch to another correlation type when Pearson doesn't fit.Why is that wrong?

      The method parameter currently accepts only "pearson". This API computes Pearson correlation and nothing else.

      Covered in Pearson correlation from a DataFrame

    2. 2.Adding DISTINCT to the SQL corr aggregate is harmless and can't change the coefficient.Why is that wrong?

      DISTINCT removes duplicate (expr1, expr2) pairs before the calculation. On the documented data it changes the result from 0.816496580927726 to 0.8660254037844387.

      Covered in The SQL corr aggregate: DISTINCT and FILTER

    3. 3.The first column of a crosstab result keeps the name of col1, and value pairs that never occur come back as nulls.Why is that wrong?

      The first column is named $col1_$col2 (for example c1_c2), and pairs with no occurrences are filled with zero counts.

      Covered in Two categorical features: the contingency table

    4. 4.cov and corr are two names for the same Pearson statistic, since both take two columns and return a double.Why is that wrong?

      cov returns the sample covariance, which is a different number. On the same c1/c2 data, cov gives -18.0 and corr gives -0.3592106040535498.

      Covered in Covariance is not the same number

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Functionality for statistical functions with a DataFrame.”
      ↩︎ Start with the feature types
      “Calculates the sample covariance for the given columns as a double value.”
      ↩︎ Checkpoint
    2. 2.
      “Computes a pair-wise frequency table of the given columns. Also known as a contingency table.”
      ↩︎ Start with the feature types
      “The name of the first column will be $col1_$col2.”
      ↩︎ Two categorical features: the contingency table
      “DataFrame: Frequency matrix of two columns.”
      ↩︎ Two categorical features: the contingency table
      “The name of the first column will be $col1_$col2. Pairs that have no occurrences will have zero as their counts.”
      ↩︎ Exam trap 3
      “Pairs that have no occurrences will have zero as their counts.”
      ↩︎ Checkpoint
    3. 3.
      “DataFrame.corr and DataFrameStatFunctions.corr are aliases of each other.”
      ↩︎ Pearson correlation from a DataFrame
      “Calculates the correlation of two columns of a DataFrame as a double value.”
      ↩︎ Key concept
      “The correlation method. Currently only supports "pearson".”
      ↩︎ Exam trap 1
    4. 4.
      “Returns a new Column for the Pearson Correlation Coefficient for col1 and col2.”
      ↩︎ Pearson correlation from a DataFrame
    5. 5.
      “expr1: An expression that evaluates to a numeric.”
      ↩︎ The SQL corr aggregate: DISTINCT and FILTER
      “This function can also be invoked as a window function using the OVER clause.”
      ↩︎ The SQL corr aggregate: DISTINCT and FILTER
      “If DISTINCT is specified the function operates only on a unique set of expr1, expr2 pairs.”
      ↩︎ Exam trap 2
      “Returns Pearson coefficient of correlation between a group of number pairs.”
      ↩︎ Checkpoint
      “If DISTINCT is specified the function operates only on a unique set of expr1, expr2 pairs.”
      ↩︎ Prediction
      “cond: An optional boolean expression filtering the rows used for aggregation.”
      ↩︎ Checkpoint
    6. 6.
      “Calculate the sample covariance for the given columns, specified by their names, as a double value.”
      ↩︎ Covariance is not the same number
      “Calculate the sample covariance for the given columns, specified by their names, as a double value.”
      ↩︎ Exam trap 4
    7. 7.
      “DataFrame.crosstab and DataFrameStatFunctions.crosstab are aliases of each other.”
      ↩︎ Two categorical features: the contingency table

    Spotted a mistake, or was something unclear? Tell us.