A robust and flexible TypeScript library for ranking items based on pairwise comparisons using an advanced Elo-like rating system.
- ๐ Easy-to-use API for managing rankable items and their comparisons
- โ๏ธ Highly configurable ranking parameters
- ๐ง Intelligent calculation of optimal next comparison
- ๐ Comprehensive progress tracking for ranking stability
- ๐ Detailed item statistics and historical data
- ๐ง Full TypeScript support with type definitions
- ๐งฎ Transparent mathematical model with customizable parameters
Install Ranker using npm:
npm install ranker
Or using yarn:
yarn add ranker
Get up and running with Ranker in just a few lines of code:
import { Ranker, RankableItem, ComparisonResult } from "eloranker";
// Initialize items
const initialItems: RankableItem[] = [
{ id: "item1", initialRating: 1500 },
{ id: "item2", initialRating: 1500 },
{ id: "item3", initialRating: 1500 },
];
// Create a new Ranker instance
const ranker = new Ranker(initialItems, { kFactor: 32 });
// Add a comparison result
const result: ComparisonResult = {
itemId1: "item1",
itemId2: "item2",
result: "win",
timestamp: Date.now(),
};
const ratingDelta = ranker.addComparisonResult(result);
console.log(`Rating change: ${ratingDelta}`);
// Get current rankings
const rankings = ranker.getRankings();
console.log("Current rankings:", rankings);
Customize Ranker's behavior with the following configuration options:
Parameter | Type | Default | Description |
---|---|---|---|
kFactor |
number | 32 | Determines the maximum rating change per comparison |
minimumComparisons |
number | 20 | Minimum comparisons before considering an item's ranking stable |
defaultInitialRating |
number | 1500 | Starting rating for new items |
minRating |
number | 0 | Minimum possible rating for any item |
Example configuration:
const ranker = new Ranker(initialItems, {
kFactor: 24,
minimumComparisons: 30,
defaultInitialRating: 1600,
minRating: 100,
});
- kFactor: Higher values make the system more responsive but potentially volatile. Lower values provide more stability but slower adaptation.
- minimumComparisons: Increasing this value requires more data for stability, potentially increasing accuracy but increasing number of comparisons.
- defaultInitialRating: Adjust based on prior knowledge about the general strength of new items.
- minRating: Prevents extremely low ratings, which may be desirable in some applications.
Monitor ranking stability with the getProgress
method:
const progress = ranker.getProgress({
ratingChangeThreshold: 5,
stableComparisonsThreshold: 10,
});
console.log(`Ranking progress: ${progress * 100}%`);
Parameter | Type | Description |
---|---|---|
ratingChangeThreshold |
number | Maximum allowed rating change for stability |
stableComparisonsThreshold |
number | Number of recent comparisons to check |
- Lower
ratingChangeThreshold
increases accuracy but slows stabilization. - Higher
stableComparisonsThreshold
increases confidence but takes longer to achieve.
constructor(initialItems: RankableItem[], config: Partial<RankerConfig>)
Method | Description | Return Type |
---|---|---|
addItem(id: string, initialRating?: number) |
Add a new item to the ranking system | void |
removeItem(id: string) |
Remove an item from the ranking system | void |
addComparisonResult(result: ComparisonResult) |
Add a new comparison result | number (rating delta) |
getNextComparison() |
Get the optimal next comparison | [string, string] | null |
getItemStats(id: string) |
Get statistics for a specific item | RankableItem |
getRankings() |
Get current rankings of all items | RankableItem[] |
getItemCount() |
Get the total number of items | number |
getAllItems() |
Get all items in the system | RankableItem[] |
getProgress(params: ProgressParams) |
Get the current progress/stability of rankings | number |
getItemHistory(id: string) |
Get rating history for an item | Array<{ rating: number; timestamp: number }> |
type RankableItem = {
id: string;
initialRating: number;
currentRating: number;
comparisons: number;
wins: number;
losses: number;
ties: number;
lastComparisonTime: number | null;
ratingHistory: Array<{ rating: number; timestamp: number }>;
};
type ComparisonResult = {
itemId1: string;
itemId2: string;
result: "win" | "loss" | "tie";
timestamp: number;
metadata?: any;
};
type RankerConfig = {
kFactor: number;
minimumComparisons: number;
defaultInitialRating: number;
minRating: number;
};
type ProgressParams = {
ratingChangeThreshold: number;
stableComparisonsThreshold: number;
};
For systems with many items, implement batched processing:
const allItems = ranker.getAllItems();
const batchSize = 100;
for (let i = 0; i < allItems.length; i += batchSize) {
const batch = allItems.slice(i, i + batchSize);
// Process batch...
}
Use the rating delta to trigger events or updates:
const SIGNIFICANT_CHANGE_THRESHOLD = 50;
const ratingDelta = ranker.addComparisonResult(result);
if (ratingDelta > SIGNIFICANT_CHANGE_THRESHOLD) {
console.log("Significant ranking change detected!");
// Trigger updates, notifications, etc.
}
Ranker uses an advanced Elo-like rating system. Here are the key equations:
-
Expected Score Calculation:
The expected score for player A when competing against player B is:
$E(A) = \frac{1}{1 + 10^{(R_B - R_A) / 400}}$ Where
$R_A$ and$R_B$ are the current ratings of players A and B. -
Rating Update:
After a comparison, ratings are updated using:
$R_{new} = R_{old} + K \cdot (S - E)$ Where:
-
$R_{new}$ is the new rating -
$R_{old}$ is the old rating -
$K$ is the k-factor (configurable) -
$S$ is the actual score (1 for win, 0.5 for tie, 0 for loss) -
$E$ is the expected score from step 1
-
-
Rating Delta:
The rating delta returned by
addComparisonResult
is:$\Delta = |R_{new_A} - R_{old_A}| + |R_{new_B} - R_{old_B}|$ This represents the total change in ratings for both items.
These equations ensure:
- Winning against a higher-rated opponent yields a larger rating increase.
- Losing against a lower-rated opponent results in a larger rating decrease.
- The k-factor controls the maximum possible change from a single comparison.
We welcome contributions to Ranker! Here's how you can help:
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request