Module 9 · Dependence, Regression, and Model Foundations Lesson 84 of 120
Spearman Rank Correlation and Kendall Tau
Comparing monotone rankings and pair ordering.
Transcript
18 sentences · select one to jump thereCheck your understanding
Is the Kendall convention in this course tau-b?
Code lab
Run it yourself
The lesson source in 7 languages. Edit it, run TypeScript and Python right here, and compare with the expected output.
/**
* Fintech Math Bootcamp · Lesson 084 of 120
* Spearman Rank Correlation and Kendall Tau
* Module 09: Dependence, Regression, and Model Foundations
*
* Scenario: Comparing monotone rankings and pair ordering
* Rule: Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)
*
* Try it: Is the Kendall convention in this course tau-b?
*
* Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
* Free course: https://courses.thefintechbuilder.com
* Synthetic teaching example, not financial advice or a production library.
*/
export function lesson084() {
const x=[1,2,3,4,5], y=[2,4,5,4,5];
const ranks=(a:number[])=>a.map(v=>1+a.filter(z=>z<v).length+(a.filter(z=>z===v).length-1)/2);
const n=x.length, rx=ranks(x), ry=ranks(y), m=(n+1)/2; // mean rank
const cross=rx.reduce((s,v,i)=>s+(v-m)*(ry[i]-m),0);
const ss=(a:number[])=>a.reduce((s,v)=>s+(v-m)**2,0);
let signedPairs=0;
for(let i=0;i<n;i++) for(let j=i+1;j<n;j++)
signedPairs+=Math.sign((x[j]-x[i])*(y[j]-y[i]));
const result={rx,ry,spearman:cross/Math.sqrt(ss(rx)*ss(ry)),tauA:signedPairs/(n*(n-1)/2)};
return result;
}
export const checkedResult = {"rx":[1,2,3,4,5],"ry":[1,2.5,4.5,2.5,4.5],"spearman":0.7378647873726218,"tauA":0.6};
// Run this file directly: npx tsx lessons/09-dependence-regression-and-model-foundations/084-spearman-rank-correlation-and-kendall-tau.ts
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/").split("/").pop()!)) {
console.log(JSON.stringify(lesson084(), null, 2));
}
Your output
Press Run to execute the code in your browser.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}"""
Fintech Math Bootcamp · Lesson 084 of 120
Spearman Rank Correlation and Kendall Tau
Module 09: Dependence, Regression, and Model Foundations
Scenario: Comparing monotone rankings and pair ordering
Rule: Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)
Try it: Is the Kendall convention in this course tau-b?
Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
Free course: https://courses.thefintechbuilder.com
Synthetic teaching example, not financial advice or a production library.
"""
import json
import math
def sign(v):
return (v > 0) - (v < 0)
def ranks(a):
"""Average ranks: ties share the mean of the positions they occupy."""
return [1 + sum(1 for z in a if z < v) + (sum(1 for z in a if z == v) - 1) / 2 for v in a]
def lesson_084():
x, y = [1, 2, 3, 4, 5], [2, 4, 5, 4, 5]
n, rx, ry = len(x), ranks(x), ranks(y)
m = (n + 1) / 2 # mean rank
cross = 0
for a, b in zip(rx, ry):
cross += (a - m) * (b - m)
def ss(a):
total = 0
for v in a:
total += (v - m) ** 2
return total
signed_pairs = 0
for i in range(n):
for j in range(i + 1, n):
signed_pairs += sign((x[j] - x[i]) * (y[j] - y[i]))
return {
"rx": rx,
"ry": ry,
"spearman": cross / math.sqrt(ss(rx) * ss(ry)),
"tauA": signed_pairs / (n * (n - 1) / 2),
}
if __name__ == "__main__":
print(json.dumps(lesson_084(), indent=2))
Your output
Press Run to execute the code in your browser.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}// Fintech Math Bootcamp - Lesson 084 of 120
// Spearman Rank Correlation and Kendall Tau
// Module 09: Dependence, Regression, and Model Foundations
//
// Scenario: Comparing monotone rankings and pair ordering
// Rule: Spearman = Pearson(ranks); Kendall tau-a=(C-D)/choose(n,2)
//
// Try it: Is the Kendall convention in this course tau-b?
//
// Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
// Free course: https://courses.thefintechbuilder.com
// Synthetic teaching example, not financial advice or a production library.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Main {
// Average ranks: ties share the mean of the positions they occupy.
static double[] ranks(double[] a) {
double[] out = new double[a.length];
for (int i = 0; i < a.length; i++) {
int below = 0, equal = 0;
for (double z : a) {
if (z < a[i]) below++;
if (z == a[i]) equal++;
}
out[i] = 1 + below + (equal - 1) / 2.0;
}
return out;
}
static double sumOfSquares(double[] a, double center) {
double total = 0;
for (double v : a) total += Math.pow(v - center, 2);
return total;
}
static Map<String, Object> lesson084() {
double[] x = {1, 2, 3, 4, 5}, y = {2, 4, 5, 4, 5};
int n = x.length;
double[] rx = ranks(x), ry = ranks(y);
double m = (n + 1) / 2.0; // mean rank
double cross = 0;
for (int i = 0; i < n; i++) cross += (rx[i] - m) * (ry[i] - m);
double signedPairs = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
signedPairs += Math.signum((x[j] - x[i]) * (y[j] - y[i]));
}
}
Map<String, Object> result = new LinkedHashMap<String, Object>();
result.put("rx", rx);
result.put("ry", ry);
result.put("spearman", cross / Math.sqrt(sumOfSquares(rx, m) * sumOfSquares(ry, m)));
result.put("tauA", signedPairs / (n * (n - 1) / 2.0));
return result;
}
public static void main(String[] args) {
System.out.println(toJson(lesson084(), ""));
}
// Minimal JSON writer: two-space indent, whole numbers without a decimal point, NaN as null.
static String toJson(Object value, String indent) {
if (value == null) return "null";
if (value instanceof Boolean) return value.toString();
if (value instanceof Number) return formatNumber(((Number) value).doubleValue());
if (value instanceof String) return quote((String) value);
if (value instanceof double[]) {
List<Object> boxed = new ArrayList<Object>();
for (double d : (double[]) value) boxed.add(d);
return toJson(boxed, indent);
}
if (value instanceof Object[]) return toJson(Arrays.asList((Object[]) value), indent);
String inner = indent + " ";
StringBuilder out = new StringBuilder();
if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
if (map.isEmpty()) return "{}";
out.append("{\n");
int i = 0;
for (Map.Entry<?, ?> entry : map.entrySet()) {
out.append(inner).append(quote(entry.getKey().toString())).append(": ")
.append(toJson(entry.getValue(), inner));
out.append(++i < map.size() ? ",\n" : "\n");
}
return out.append(indent).append("}").toString();
}
List<?> list = (List<?>) value;
if (list.isEmpty()) return "[]";
out.append("[\n");
for (int i = 0; i < list.size(); i++) {
out.append(inner).append(toJson(list.get(i), inner));
out.append(i + 1 < list.size() ? ",\n" : "\n");
}
return out.append(indent).append("]").toString();
}
static String formatNumber(double x) {
if (Double.isNaN(x) || Double.isInfinite(x)) return "null";
if (x == Math.rint(x) && Math.abs(x) < 1e15) return Long.toString((long) x);
return Double.toString(x);
}
static String quote(String s) {
StringBuilder out = new StringBuilder("\"");
for (char c : s.toCharArray()) {
if (c == '"' || c == '\\') out.append('\\').append(c);
else if (c == '\n') out.append("\\n");
else if (c < 0x20) out.append(String.format("\\u%04x", (int) c));
else out.append(c);
}
return out.append('"').toString();
}
}
No browser runner for Java yet
Read the code here, then run it in your own toolchain or a ready-made cloud workspace.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}// Fintech Math Bootcamp · Lesson 084 of 120
// Spearman Rank Correlation and Kendall Tau
// Module 09: Dependence, Regression, and Model Foundations
//
// Scenario: Comparing monotone rankings and pair ordering
// Rule: Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)
//
// Try it: Is the Kendall convention in this course tau-b?
//
// Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
// Free course: https://courses.thefintechbuilder.com
// Synthetic teaching example, not financial advice or a production library.
package main
import (
"encoding/json"
"fmt"
"math"
)
type Lesson084Result struct {
Rx []float64 `json:"rx"`
Ry []float64 `json:"ry"`
Spearman float64 `json:"spearman"`
TauA float64 `json:"tauA"`
}
// ranks assigns average ranks: ties share the mean of the positions they occupy.
func ranks(a []float64) []float64 {
out := make([]float64, len(a))
for i, v := range a {
below, equal := 0, 0
for _, z := range a {
if z < v {
below++
}
if z == v {
equal++
}
}
out[i] = 1 + float64(below) + float64(equal-1)/2
}
return out
}
func sign(v float64) float64 {
switch {
case v > 0:
return 1
case v < 0:
return -1
}
return 0
}
func lesson084() Lesson084Result {
x := []float64{1, 2, 3, 4, 5}
y := []float64{2, 4, 5, 4, 5}
n := len(x)
rx, ry := ranks(x), ranks(y)
m := float64(n+1) / 2 // mean rank
cross := 0.0
for i := range rx {
cross += (rx[i] - m) * (ry[i] - m)
}
ss := func(a []float64) float64 {
total := 0.0
for _, v := range a {
total += math.Pow(v-m, 2)
}
return total
}
signedPairs := 0.0
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
signedPairs += sign((x[j] - x[i]) * (y[j] - y[i]))
}
}
return Lesson084Result{
Rx: rx,
Ry: ry,
Spearman: cross / math.Sqrt(ss(rx)*ss(ry)),
TauA: signedPairs / float64(n*(n-1)/2),
}
}
func main() {
out, err := json.MarshalIndent(lesson084(), "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(out))
}
No browser runner for Go yet
Read the code here, then run it in your own toolchain or a ready-made cloud workspace.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}// Fintech Math Bootcamp · Lesson 084 of 120
// Spearman Rank Correlation and Kendall Tau
// Module 09: Dependence, Regression, and Model Foundations
//
// Scenario: Comparing monotone rankings and pair ordering
// Rule: Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)
//
// Try it: Is the Kendall convention in this course tau-b?
//
// Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
// Free course: https://courses.thefintechbuilder.com
// Synthetic teaching example, not financial advice or a production library.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
// A minimal JSON value, enough to print this lesson's result.
struct Json {
enum class Kind { Null, Bool, Number, String, Array, Object };
Kind kind = Kind::Null;
bool flag = false;
double number = 0.0;
std::string text;
std::vector<std::string> keys; // object keys, parallel to items
std::vector<Json> items; // array elements or object values
Json() = default;
Json(bool value) : kind(Kind::Bool), flag(value) {}
Json(int value) : kind(Kind::Number), number(value) {}
Json(double value) : kind(Kind::Number), number(value) {}
Json(const char* value) : kind(Kind::String), text(value) {}
Json(const std::string& value) : kind(Kind::String), text(value) {}
Json(const std::vector<double>& values) : kind(Kind::Array) {
for (double v : values) items.push_back(Json(v));
}
};
Json jsonArray(const std::vector<Json>& values) {
Json array;
array.kind = Json::Kind::Array;
array.items = values;
return array;
}
Json jsonObject(const std::vector<std::pair<std::string, Json>>& fields) {
Json object;
object.kind = Json::Kind::Object;
for (const auto& field : fields) {
object.keys.push_back(field.first);
object.items.push_back(field.second);
}
return object;
}
// Shortest decimal form that reads back as the same double.
std::string formatNumber(double x) {
if (!std::isfinite(x)) return "null";
char buffer[32];
if (x == std::floor(x) && std::fabs(x) < 1e15) {
std::snprintf(buffer, sizeof buffer, "%.0f", x);
return buffer;
}
for (int precision = 1; precision <= 17; ++precision) {
std::snprintf(buffer, sizeof buffer, "%.*g", precision, x);
if (std::strtod(buffer, nullptr) == x) break;
}
return buffer;
}
std::string quote(const std::string& s) {
std::string out = "\"";
for (char c : s) {
if (c == '"' || c == '\\') { out += '\\'; out += c; }
else if (c == '\n') out += "\\n";
else out += c;
}
return out + "\"";
}
std::string toJson(const Json& value, const std::string& indent = "") {
switch (value.kind) {
case Json::Kind::Null: return "null";
case Json::Kind::Bool: return value.flag ? "true" : "false";
case Json::Kind::Number: return formatNumber(value.number);
case Json::Kind::String: return quote(value.text);
default: break;
}
const bool isObject = value.kind == Json::Kind::Object;
if (value.items.empty()) return isObject ? "{}" : "[]";
const std::string inner = indent + " ";
std::string out = isObject ? "{\n" : "[\n";
for (std::size_t i = 0; i < value.items.size(); ++i) {
out += inner;
if (isObject) out += quote(value.keys[i]) + ": ";
out += toJson(value.items[i], inner);
out += i + 1 < value.items.size() ? ",\n" : "\n";
}
return out + indent + (isObject ? "}" : "]");
}
// Average ranks: ties share the mean of the positions they occupy.
std::vector<double> ranks(const std::vector<double>& a) {
std::vector<double> out;
for (double v : a) {
int below = 0, equal = 0;
for (double z : a) {
if (z < v) ++below;
if (z == v) ++equal;
}
out.push_back(1 + below + (equal - 1) / 2.0);
}
return out;
}
double sign(double v) { return (v > 0) - (v < 0); }
Json lesson084() {
const std::vector<double> x = {1, 2, 3, 4, 5}, y = {2, 4, 5, 4, 5};
const int n = static_cast<int>(x.size());
const std::vector<double> rx = ranks(x), ry = ranks(y);
const double m = (n + 1) / 2.0; // mean rank
double cross = 0.0;
for (int i = 0; i < n; ++i) cross += (rx[i] - m) * (ry[i] - m);
auto ss = [m](const std::vector<double>& a) {
double total = 0.0;
for (double v : a) total += std::pow(v - m, 2);
return total;
};
double signedPairs = 0.0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) signedPairs += sign((x[j] - x[i]) * (y[j] - y[i]));
}
return jsonObject({
{"rx", rx},
{"ry", ry},
{"spearman", cross / std::sqrt(ss(rx) * ss(ry))},
{"tauA", signedPairs / (n * (n - 1) / 2.0)},
});
}
int main() {
std::cout << toJson(lesson084()) << '\n';
return 0;
}
No browser runner for C++ yet
Read the code here, then run it in your own toolchain or a ready-made cloud workspace.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}// Fintech Math Bootcamp · Lesson 084 of 120
// Spearman Rank Correlation and Kendall Tau
// Module 09: Dependence, Regression, and Model Foundations
//
// Scenario: Comparing monotone rankings and pair ordering
// Rule: Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)
//
// Try it: Is the Kendall convention in this course tau-b?
//
// Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
// Free course: https://courses.thefintechbuilder.com
// Synthetic teaching example, not financial advice or a production library.
/// A minimal JSON value, enough to print this lesson's result.
#[allow(dead_code)]
enum Json {
Null,
Bool(bool),
Num(f64),
Str(String),
Arr(Vec<Json>),
Obj(Vec<(String, Json)>),
}
#[allow(dead_code)]
impl Json {
fn obj(fields: Vec<(&str, Json)>) -> Json {
Json::Obj(fields.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
}
fn nums(values: &[f64]) -> Json {
Json::Arr(values.iter().map(|&v| Json::Num(v)).collect())
}
/// Pretty-prints with two-space indentation.
fn pretty(&self, indent: &str) -> String {
let inner = format!("{} ", indent);
match self {
Json::Null => "null".to_string(),
Json::Bool(b) => b.to_string(),
Json::Num(x) => format_number(*x),
Json::Str(s) => quote(s),
Json::Arr(items) if items.is_empty() => "[]".to_string(),
Json::Obj(fields) if fields.is_empty() => "{}".to_string(),
Json::Arr(items) => {
let body: Vec<String> = items
.iter()
.map(|v| format!("{}{}", inner, v.pretty(&inner)))
.collect();
format!("[\n{}\n{}]", body.join(",\n"), indent)
}
Json::Obj(fields) => {
let body: Vec<String> = fields
.iter()
.map(|(k, v)| format!("{}{}: {}", inner, quote(k), v.pretty(&inner)))
.collect();
format!("{{\n{}\n{}}}", body.join(",\n"), indent)
}
}
}
}
fn format_number(x: f64) -> String {
if !x.is_finite() {
"null".to_string()
} else if x == x.trunc() && x.abs() < 1e15 {
format!("{}", x as i64)
} else {
format!("{}", x)
}
}
fn quote(s: &str) -> String {
let mut out = String::from("\"");
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
c => out.push(c),
}
}
out.push('"');
out
}
/// Average ranks: ties share the mean of the positions they occupy.
fn ranks(a: &[f64]) -> Vec<f64> {
a.iter()
.map(|&v| {
let below = a.iter().filter(|&&z| z < v).count() as f64;
let equal = a.iter().filter(|&&z| z == v).count() as f64;
1.0 + below + (equal - 1.0) / 2.0
})
.collect()
}
/// Sign like JavaScript's Math.sign: zero stays zero.
fn sign(v: f64) -> f64 {
if v > 0.0 {
1.0
} else if v < 0.0 {
-1.0
} else {
0.0
}
}
fn lesson_084() -> Json {
let x = [1.0_f64, 2.0, 3.0, 4.0, 5.0];
let y = [2.0_f64, 4.0, 5.0, 4.0, 5.0];
let n = x.len();
let (rx, ry) = (ranks(&x), ranks(&y));
let m = (n + 1) as f64 / 2.0; // mean rank
let cross = rx
.iter()
.zip(ry.iter())
.fold(0.0_f64, |s, (a, b)| s + (a - m) * (b - m));
let ss = |a: &[f64]| a.iter().fold(0.0_f64, |s, v| s + (v - m).powf(2.0));
let mut signed_pairs = 0.0_f64;
for i in 0..n {
for j in i + 1..n {
signed_pairs += sign((x[j] - x[i]) * (y[j] - y[i]));
}
}
Json::obj(vec![
("rx", Json::nums(&rx)),
("ry", Json::nums(&ry)),
("spearman", Json::Num(cross / (ss(&rx[..]) * ss(&ry[..])).sqrt())),
("tauA", Json::Num(signed_pairs / (n * (n - 1) / 2) as f64)),
])
}
fn main() {
println!("{}", lesson_084().pretty(""));
}
No browser runner for Rust yet
Read the code here, then run it in your own toolchain or a ready-made cloud workspace.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}// Fintech Math Bootcamp · Lesson 084 of 120
// Spearman Rank Correlation and Kendall Tau
// Module 09: Dependence, Regression, and Model Foundations
//
// Scenario: Comparing monotone rankings and pair ordering
// Rule: Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)
//
// Try it: Is the Kendall convention in this course tau-b?
//
// Lesson article: https://thefintechbuilder.com/financial-mathematics-statistics-and-data-foundations/dependence-regression-and-model-foundations/spearman-rank-correlation-and-kendall-tau/
// Free course: https://courses.thefintechbuilder.com
// Synthetic teaching example, not financial advice or a production library.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine(JsonSerializer.Serialize(Lesson084(), options));
// Average ranks: ties share the mean of the positions they occupy.
static double[] Ranks(double[] a) =>
a.Select(v => 1 + a.Count(z => z < v) + (a.Count(z => z == v) - 1) / 2.0).ToArray();
static object Lesson084()
{
double[] x = { 1, 2, 3, 4, 5 }, y = { 2, 4, 5, 4, 5 };
int n = x.Length;
double[] rx = Ranks(x), ry = Ranks(y);
double m = (n + 1) / 2.0; // mean rank
double cross = rx.Select((v, i) => (v - m) * (ry[i] - m)).Aggregate(0.0, (s, p) => s + p);
double Ss(double[] a) => a.Aggregate(0.0, (s, v) => s + Math.Pow(v - m, 2));
double signedPairs = 0;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
signedPairs += Math.Sign((x[j] - x[i]) * (y[j] - y[i]));
return new
{
rx,
ry,
spearman = cross / Math.Sqrt(Ss(rx) * Ss(ry)),
tauA = signedPairs / (n * (n - 1) / 2.0),
};
}
No browser runner for C# yet
Read the code here, then run it in your own toolchain or a ready-made cloud workspace.
Expected output
{
"rx": [
1,
2,
3,
4,
5
],
"ry": [
1,
2.5,
4.5,
2.5,
4.5
],
"spearman": 0.7378647873726218,
"tauA": 0.6
}Prefer your own machine? Every file is in the course repository · open it in Codespaces.
Lesson notes
The rule
Spearman = Pearson(ranks); Kendall tau-a=(C−D)/choose(n,2)