How to Calculate Kendall's Tau in Python

Kendall's Tau (τ) is a rank correlation coefficient that measures the ordinal association between two variables. Unlike Pearson's correlation, which assumes linear relationships and continuous data,...

Key Insights

  • Kendall’s Tau is the go-to correlation coefficient for ordinal data, small samples, or when you suspect non-linear monotonic relationships—it’s more robust than Pearson and often more interpretable than Spearman.
  • SciPy’s kendalltau function handles the heavy lifting, but understanding concordant vs. discordant pairs helps you explain results to stakeholders and catch edge cases.
  • Always report both the tau value and p-value; a strong correlation is meaningless without statistical significance, especially with small samples.

Introduction to Kendall’s Tau

Kendall’s Tau (τ) is a rank correlation coefficient that measures the ordinal association between two variables. Unlike Pearson’s correlation, which assumes linear relationships and continuous data, Kendall’s Tau works with ranks and captures any monotonic relationship.

Use Kendall’s Tau when:

  • Your data is ordinal (rankings, Likert scales, grades)
  • You have small sample sizes (under 30 observations)
  • You suspect non-linear but monotonic relationships
  • Your data contains outliers that would distort Pearson’s r
  • You want a more robust, interpretable measure

Three variants exist. Tau-a is the simplest form but doesn’t account for ties. Tau-b adjusts for ties and is the default in most implementations—use this for square tables or when both variables can have ties. Tau-c (Stuart’s Tau-c) is designed for rectangular tables where the number of categories differs between variables.

For most practical applications, Tau-b is your best choice. It’s what SciPy calculates by default.

The Math Behind Kendall’s Tau

Understanding the calculation helps you interpret results and debug unexpected values. Kendall’s Tau compares all possible pairs of observations and classifies them as concordant or discordant.

A concordant pair occurs when the ranking of both elements agrees: if observation A ranks higher than B on variable X, it also ranks higher on variable Y. A discordant pair is the opposite: the rankings disagree.

The basic formula for Tau-a is:

τ = (C - D) / (n(n-1)/2)

Where C is concordant pairs, D is discordant pairs, and n(n-1)/2 is the total number of pairs.

Let’s implement this manually to cement the concept:

import numpy as np
from itertools import combinations

def calculate_kendall_tau_manual(x, y):
    """
    Manual calculation of Kendall's Tau-a for educational purposes.
    Does not handle ties—use scipy for production code.
    """
    n = len(x)
    concordant = 0
    discordant = 0
    
    # Compare all pairs
    for i, j in combinations(range(n), 2):
        x_diff = x[i] - x[j]
        y_diff = y[i] - y[j]
        
        # Check if signs agree (concordant) or disagree (discordant)
        product = x_diff * y_diff
        if product > 0:
            concordant += 1
        elif product < 0:
            discordant += 1
        # product == 0 means a tie, ignored in Tau-a
    
    total_pairs = n * (n - 1) // 2
    tau = (concordant - discordant) / total_pairs
    
    return tau, concordant, discordant

# Example: Student exam ranks vs. study hours ranks
exam_ranks = [1, 2, 3, 4, 5]
study_ranks = [1, 3, 2, 5, 4]

tau, c, d = calculate_kendall_tau_manual(exam_ranks, study_ranks)
print(f"Concordant pairs: {c}")
print(f"Discordant pairs: {d}")
print(f"Kendall's Tau: {tau:.3f}")

Output:

Concordant pairs: 8
Discordant pairs: 2
Kendall's Tau: 0.600

The tau value ranges from -1 (perfect negative association) to +1 (perfect positive association). Zero indicates no association. A tau of 0.6 suggests a moderately strong positive relationship—when one variable increases, the other tends to increase as well.

Using SciPy’s kendalltau Function

For production code, use SciPy. It handles ties correctly, calculates p-values, and is optimized for performance.

from scipy import stats
import numpy as np

# Same data as before
exam_ranks = np.array([1, 2, 3, 4, 5])
study_ranks = np.array([1, 3, 2, 5, 4])

# Calculate Kendall's Tau
result = stats.kendalltau(exam_ranks, study_ranks)

print(f"Tau: {result.statistic:.4f}")
print(f"P-value: {result.pvalue:.4f}")

# Alternative: unpack directly
tau, p_value = stats.kendalltau(exam_ranks, study_ranks)
print(f"\nTau: {tau:.4f}, p-value: {p_value:.4f}")

The method parameter controls tie handling. Options include 'auto' (default, chooses based on data size), 'asymptotic' (uses normal approximation), and 'exact' (computes exact p-value, slow for large n).

# For small samples, exact p-values are more accurate
tau, p = stats.kendalltau(exam_ranks, study_ranks, method='exact')
print(f"Exact method - Tau: {tau:.4f}, p-value: {p:.4f}")

# For large samples, asymptotic is faster and sufficient
large_x = np.random.randn(1000)
large_y = large_x + np.random.randn(1000) * 0.5
tau, p = stats.kendalltau(large_x, large_y, method='asymptotic')
print(f"Asymptotic method - Tau: {tau:.4f}, p-value: {p:.4f}")

Pandas Integration for DataFrames

When analyzing multiple variables, Pandas makes correlation matrices trivial:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# Sample survey data
data = pd.DataFrame({
    'satisfaction': [4, 5, 3, 4, 2, 5, 3, 4, 5, 2],
    'likelihood_recommend': [5, 5, 3, 4, 2, 4, 3, 5, 5, 1],
    'purchase_frequency': [3, 4, 2, 3, 1, 5, 2, 4, 4, 1],
    'years_customer': [2, 5, 1, 3, 1, 4, 2, 3, 5, 1]
})

# Calculate Kendall correlation matrix
kendall_corr = data.corr(method='kendall')
print(kendall_corr.round(3))

# Visualize with a heatmap
plt.figure(figsize=(8, 6))
sns.heatmap(
    kendall_corr,
    annot=True,
    fmt='.2f',
    cmap='RdBu_r',
    center=0,
    vmin=-1,
    vmax=1,
    square=True
)
plt.title("Kendall's Tau Correlation Matrix")
plt.tight_layout()
plt.savefig('kendall_heatmap.png', dpi=150)
plt.show()

Note that df.corr(method='kendall') doesn’t provide p-values. For significance testing on all pairs, you’ll need a custom approach covered in the next sections.

Handling Real-World Data Challenges

Real data is messy. Here’s how to handle common issues:

import pandas as pd
import numpy as np
from scipy import stats

def clean_for_correlation(df, col1, col2, handle_missing='drop'):
    """
    Prepare two columns for Kendall's Tau calculation.
    
    Parameters:
    -----------
    handle_missing : str
        'drop' - remove rows with any NaN
        'mean' - impute with column mean (use cautiously)
    """
    subset = df[[col1, col2]].copy()
    
    # Report missing data
    missing = subset.isna().sum()
    if missing.any():
        print(f"Missing values - {col1}: {missing[col1]}, {col2}: {missing[col2]}")
    
    if handle_missing == 'drop':
        subset = subset.dropna()
    elif handle_missing == 'mean':
        subset = subset.fillna(subset.mean())
    
    # Check for constant columns (would cause issues)
    for col in [col1, col2]:
        if subset[col].nunique() == 1:
            raise ValueError(f"Column '{col}' has no variance after cleaning")
    
    return subset[col1].values, subset[col2].values

# Example with messy data
messy_data = pd.DataFrame({
    'rating': [4, 5, np.nan, 3, 4, 5, 2, np.nan, 4, 3],
    'score': [85, 90, 75, np.nan, 80, 95, 70, 85, 88, 72]
})

x, y = clean_for_correlation(messy_data, 'rating', 'score')
tau, p = stats.kendalltau(x, y)
print(f"After cleaning - n={len(x)}, Tau: {tau:.3f}, p: {p:.3f}")

Tied ranks are handled automatically by SciPy’s Tau-b implementation, but heavy ties reduce the maximum possible correlation. If more than 20% of your data consists of ties, consider whether your measurement scale is appropriate.

Large datasets can slow down computation. Kendall’s Tau is O(n²) in the naive implementation, though SciPy uses an O(n log n) algorithm. For datasets over 100,000 rows, consider sampling:

def kendall_with_sampling(x, y, sample_size=10000, n_iterations=5):
    """
    Estimate Kendall's Tau on large datasets using sampling.
    Returns mean tau and confidence interval.
    """
    taus = []
    n = len(x)
    
    for _ in range(n_iterations):
        idx = np.random.choice(n, size=min(sample_size, n), replace=False)
        tau, _ = stats.kendalltau(x[idx], y[idx])
        taus.append(tau)
    
    return np.mean(taus), np.std(taus) * 1.96  # 95% CI

Statistical Significance and Interpretation

A tau value means nothing without context. Here’s a utility function for proper reporting:

from scipy import stats

def report_kendall(x, y, alpha=0.05):
    """
    Calculate and report Kendall's Tau with significance indicators.
    
    Returns a formatted string suitable for reports.
    """
    tau, p = stats.kendalltau(x, y)
    n = len(x)
    
    # Significance stars
    if p < 0.001:
        stars = '***'
    elif p < 0.01:
        stars = '**'
    elif p < 0.05:
        stars = '*'
    else:
        stars = ''
    
    # Effect size interpretation (Cohen's guidelines adapted)
    abs_tau = abs(tau)
    if abs_tau < 0.1:
        effect = 'negligible'
    elif abs_tau < 0.2:
        effect = 'small'
    elif abs_tau < 0.3:
        effect = 'moderate'
    else:
        effect = 'large'
    
    # Direction
    direction = 'positive' if tau > 0 else 'negative'
    
    significant = p < alpha
    sig_text = 'significant' if significant else 'not significant'
    
    report = (
        f"τ = {tau:.3f}{stars}, p = {p:.4f}, n = {n}\n"
        f"Interpretation: {effect} {direction} association, {sig_text} at α = {alpha}"
    )
    
    return report, {'tau': tau, 'p': p, 'n': n, 'significant': significant}

# Usage
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
y = np.array([2, 1, 4, 3, 6, 5, 8, 7, 10, 9])

report, stats_dict = report_kendall(x, y)
print(report)

For small samples (n < 10), be skeptical of p-values. The exact test is more reliable than asymptotic approximations, but even then, you need sufficient data to detect real associations.

Practical Application Example

Let’s tie everything together with a realistic analysis:

import pandas as pd
import numpy as np
from scipy import stats
import seaborn as sns
import matplotlib.pyplot as plt

# Simulated customer survey data
np.random.seed(42)
n_customers = 150

# Generate correlated ordinal data
base = np.random.randn(n_customers)
data = pd.DataFrame({
    'customer_id': range(1, n_customers + 1),
    'satisfaction': np.clip(np.round(3 + base + np.random.randn(n_customers) * 0.5), 1, 5).astype(int),
    'recommend_likelihood': np.clip(np.round(3 + base * 0.8 + np.random.randn(n_customers) * 0.6), 1, 5).astype(int),
    'purchase_frequency': np.clip(np.round(2 + base * 0.6 + np.random.randn(n_customers) * 0.8), 1, 5).astype(int),
    'support_rating': np.clip(np.round(3 + np.random.randn(n_customers)), 1, 5).astype(int)
})

print("Sample data:")
print(data.head(10))
print(f"\nDataset shape: {data.shape}")

# Calculate all pairwise Kendall correlations with p-values
numeric_cols = ['satisfaction', 'recommend_likelihood', 'purchase_frequency', 'support_rating']

results = []
for i, col1 in enumerate(numeric_cols):
    for col2 in numeric_cols[i+1:]:
        tau, p = stats.kendalltau(data[col1], data[col2])
        results.append({
            'Variable 1': col1,
            'Variable 2': col2,
            'Tau': tau,
            'P-value': p,
            'Significant': p < 0.05
        })

results_df = pd.DataFrame(results)
print("\nPairwise Kendall's Tau Correlations:")
print(results_df.to_string(index=False))

# Key finding: satisfaction vs purchase frequency
tau, p = stats.kendalltau(data['satisfaction'], data['purchase_frequency'])
print(f"\n--- Key Finding ---")
print(f"Satisfaction → Purchase Frequency: τ = {tau:.3f}, p = {p:.4f}")
print(f"Customers with higher satisfaction tend to purchase more frequently.")

This workflow covers data preparation, pairwise analysis with significance testing, and clear interpretation. Adapt it to your specific domain—the pattern remains the same whether you’re analyzing survey responses, medical outcomes, or user behavior rankings.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.