PokerDF
Converts poker hand history files into structured Pandas DataFrames, making it easier to analyze your games.
Fast and reliable, PokerDF is able to process 3,000 hand history files into .parquet per minute, in a MacBook Air M2 with 8-core CPU.
Currently supports PokerStars. Make sure hand histories are saved in English.
Introduction
Converting raw hand histories into structured data is the first step toward building a solid poker strategy and maximizing ROI. What are the optimal VPIP, PFR, and C-BET frequencies for No Limit Hold'em 6-Max? In which specific situations is a 3-Bet most profitable? When is bluffing a clear mistake? Once your data is organized in a Pandas DataFrame, the analytical explorations become unlimited, opening new possibilities to fine-tune your decision-making.
In the processed DataFrame, each row corresponds to a specific player in a specific hand, containing all relevant information about that instance of the game. Below, you’ll find an example of hand history before and after processing.
Before
PokerStars Hand #219372022626: Tournament #3026510091, $1.84+$0.16 USD Hold'em No Limit - Level I (10/20) - 2020/10/14 10:33:59 BRT [2020/10/14 9:33:59 ET]
Table '3026510091 1' 3-max Seat #1 is the button
Seat 1: VillainA (500 in chips)
Seat 2: garciamurilo (500 in chips)
Seat 3: VillainB (500 in chips)
garciamurilo: posts small blind 10
VillainB: posts big blind 20
*** HOLE CARDS ***
Dealt to garciamurilo [6h Ks]
VillainB is disconnected
VillainA: folds
garciamurilo: calls 10
VillainB: checks
*** FLOP *** [4d Qs Qd]
garciamurilo: checks
VillainB: checks
*** TURN *** [4d Qs Qd] [3s]
garciamurilo: checks
VillainB: bets 20
garciamurilo: folds
Uncalled bet (20) returned to VillainB
VillainB collected 40 from pot
VillainB: doesn't show hand
*** SUMMARY ***
Total pot 40 | Rake 0
Board [4d Qs Qd 3s]
Seat 1: VillainA (button) folded before Flop (didn't bet)
Seat 2: garciamurilo (small blind) folded on the Turn
Seat 3: VillainB (big blind) collected (40)
After
| Modality | TableSize | BuyIn | TournID | TableID | HandID | LocalTime | Level | Ante | Blinds | Owner | OwnersHand | Playing | Player | Seat | PostedAnte | Position | PostedBlind | Stack | Bounty | PreflopAction | FlopAction | TurnAction | RiverAction | AnteAllIn | PreflopAllIn | FlopAllIn | TurnAllIn | RiverAllIn | BoardFlop | BoardTurn | BoardRiver | ShowDown | CardCombination | Result | Balance | UncalledReturned | BountyWon | TotalPotLog | Rake | PotBreakdown | FinalRank | Prize | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | USD Hold'em No Limit | 3 | $1.84+$0.16 | 3026510091 | 1 | 219372022626 | 2020-10-14 10:33:59 | I | None | [10.0, 20.0] | garciamurilo | ['6h', 'Ks'] | 3 | VillainA | 1 | None | button | nan | 500 | nan | ['folds', ''] | ['', ''] | ['', ''] | ['', ''] | False | False | False | False | False | ['4d', 'Qs', 'Qd'] | ['4d', 'Qs', 'Qd', '3s'] | [] | [None, None] | None | folded | nan | nan | nan | 40 | 0 | [40.0] | -1 | None |
| 1 | USD Hold'em No Limit | 3 | $1.84+$0.16 | 3026510091 | 1 | 219372022626 | 2020-10-14 10:33:59 | I | None | [10.0, 20.0] | garciamurilo | ['6h', 'Ks'] | 3 | garciamurilo | 2 | None | small blind | 10 | 500 | nan | ['calls', '10'] | ['checks', ''] | ['checks', ''], ['folds', ''] | ['', ''] | False | False | False | False | False | ['4d', 'Qs', 'Qd'] | ['4d', 'Qs', 'Qd', '3s'] | [] | [None, None] | None | folded | nan | nan | nan | 40 | 0 | [40.0] | -1 | None |
| 2 | USD Hold'em No Limit | 3 | $1.84+$0.16 | 3026510091 | 1 | 219372022626 | 2020-10-14 10:33:59 | I | None | [10.0, 20.0] | garciamurilo | ['6h', 'Ks'] | 3 | VillainB | 3 | None | big blind | 20 | 500 | nan | ['checks', ''] | ['checks', ''] | ['bets', '20'] | ['', ''] | False | False | False | False | False | ['4d', 'Qs', 'Qd'] | ['4d', 'Qs', 'Qd', '3s'] | [] | [None, None] | None | non-sd win | 40 | 20 | nan | 40 | 0 | [40.0] | -1 | None |
Installation
pip install pokerdf
Usage
First, navigate to the directory where you want to save the output:
cd output_directory
Then, run the package to convert all your hand history files:
pokerdf convert /path/to/handhistory/folder
After the process completes, you’ll see an output similar to the following:
output_directory/
└── output/
└── 20250510-105423/
├── 20200607-T2928873630.parquet
├── 20200607-T2928880893.parquet
├── 20200607-T2928925240.parquet
├── 20200607-T2928950825.parquet
├── 20200607-T2928996127.parquet
├── 20200607-T2929005994.parquet
├── ...
├── fail.txt
└── success.txt
Details
- Inside
outputyou’ll find a subfolder named with the session ID, in this case,20250510-105423, containing all .parquet files. - Each hand history file is converted into a .parquet file with the exact same structure, allowing you to concatenate them seamlessly.
- Each .parquet file follows the naming convention {DATE_OF_TOURNAMENT}-T{TOURNAMENT_ID}.parquet.
- The file
fail.txtprovides detailed information about any files that failed to process. This file is only generated if there are failures. - The file
success.txtlists all successfully converted files.
Incremental pipeline
You may want to build a pipeline to incrementally feed your table with new hand history data. In that case, you can import the convert_txt_to_tabular_data function and use it in your workflows. Refer to the docstrings and explore its usage within the package to better understand how it works.
Metadata
| Column | Description | Example | Data Type |
|---|---|---|---|
| Modality | The type of game being played | Hold'em No Limit | string |
| TableSize | Maximum number of players | 6 | int |
| BuyIn | The buy-in amount for the tournament | $4.60+$0.40 | string |
| TournID | Unique identifier for the tournament | 2928882649 | string |
| TableID | Unique identifier for the table inside a tournament | 10 | int |
| HandID | Unique identifier for the hand inside a tournament | 215024616736 | string |
| LocalTime | Local time when the hand was played | 2020-06-07 07:44:35 | datetime |
| Level | Level of the tournament | IV | string |
| Ante | Ante amount posted in the hand | 10.00 | float |
| Blinds | Big blind and small blind amounts | [10.0, 20.0] | list[float] |
| Owner | Owner of the hand history files | ownername | string |
| OwnersHand | Cards held by the owner in a specific hand | [9d, Js] | list[string] |
| Playing | Number of players active during the hand | 5 | int |
| Player | Player involved in the hand | playername | string |
| Seat | Seat number of the player | 3 | int |
| PostedAnte | Amount the player paid for the ante | 5.00 | float |
| PostedBlind | Amount the player paid for the blinds | 50.00 | float |
| Position | Player's position at the table | big blind | string |
| Stack | Current stack size of the player | 2500.00 | float |
| Bounty | Bounty on the player's head, in knockout tournaments | 0.46 | float |
| PreflopAction | Actions taken during the preflop stage | [[checks, ]] | list[list[str]] |
| FlopAction | Actions taken during the flop stage | [[bets, 840], [calls, 220]] | list[list[str]] |
| TurnAction | Actions taken during the turn stage | [[raises, 400], [calls, 500]] | list[list[str]] |
| RiverAction | Actions taken during the river stage | [[folds, ]] | list[list[str]] |
| AnteAllIn | Whether the player went all-in during the ante | True | bool |
| PreflopAllIn | Whether the player went all-in during preflop | False | bool |
| FlopAllIn | Whether the player went all-in during the flop | False | bool |
| TurnAllIn | Whether the player went all-in during the turn | False | bool |
| RiverAllIn | Whether the player went all-in during the river | False | bool |
| BoardFlop | Cards dealt on the flop | [4d, Qs, Ad] | list[string] |
| BoardTurn | Card dealt on the turn | [4d, Qs, Ad, 7d] | list[string] |
| BoardRiver | Card dealt on the river | [4d, Qs, Ad, 7d, 2d] | list[string] |
| ShowDown | Cards revealed by the player (second is null on single-card shows) | [Ah, Ac] | list[string] |
| CardCombination | Card combination held by the player | three of a kind, Aces | string |
| Result | Result of the hand (folded, lost, mucked, non-sd win, won) | won | string |
| Balance | Total value won in a hand | 9150.25 | float |
| UncalledReturned | Uncalled bets returned to the player in the hand | 600.00 | float |
| BountyWon | Bounty amount won by the player in the hand | 0.46 | float |
| TotalPotLog | Total pot of the hand, as reported in the summary | 840.00 | float |
| Rake | Rake of the hand, as reported in the summary | 0.00 | float |
| PotBreakdown | Pots of the hand: main and side pots, or the total pot alone | [5820.0, 3316.0] | list[float] |
| FinalRank | Final ranking (0 = finished without a reported place, -1 = unknown) | 1 | int |
| Prize | Prize won by the player, if any (satellite tickets: face value) | 30000.00 | float |
Data Modeling
For advanced analytics, you will need to transform the data generated with the package and explore different data models. The final structure of your data may vary depending on the specific goals of your project. You will find below a suggestion of dimensional model (star schema) split into four tables that may be useful for most cases: fact_player_actions works as the fact table, holding one row per event of a player in a hand, while dim_tourn_summary, dim_player_summary, and dim_final_rank work as dimension tables.
The reasoning behind this design:
- The fact is deliberately wide and analysis-ready. Everything that describes an event — who, where, when, with which stack, facing which board — lives on the row itself, so feature engineering needs no joins. The repetition of hand-level context (level, blinds, table size) is intentional: columnar formats like parquet compress constant-per-hand values to almost nothing, so the storage cost is negligible while every query gets simpler.
- Posts are events, not metadata. The ante and blind posts are rows like any action, carrying the real (possibly partial, when all-in) amounts. This makes the pot a pure running sum, gives a row to players that never acted voluntarily (a big blind winning a walk, an all-in on the post), and lets the dynamic
Stackbe reconstructed uniformly. - Each dimension answers one question at one grain.
dim_tourn_summarydescribes the tournament (context for slicing);dim_player_summaryholds the outcome of each player in each hand (result, amount collected, revealed cards);dim_final_rankholds the outcome of each player in the tournament. There is no hand dimension on purpose: after moving the hand context into the fact, it would keep a single attribute, and a dimension that thin is better dissolved (HandIDworks as a degenerate dimension, andLocalTimelives in the fact). - The reconstructed amounts follow the platform's own arithmetic (bet levels, short all-in blinds, calls above a short post) and were validated against the raw logs: the final
TotalPotmatches the reported "Total pot" in 100% of 135k+ real hands.
You can generate these four tables automatically with the modeling command, pointing to a folder of .parquet files produced by the convert command:
pokerdf modeling /path/to/parquet/files
The command concatenates all files and saves the four tables as .parquet inside ./modeling/{SESSION_ID}/.
fact_player_actions
One row per event of a player: the ante and blind posts open each hand as rows (with the real — possibly partial — amounts that left each stack), followed by every action, sorted exactly as the hand unfolded: rounds in chronological order, starting from the first seat to act (the seat after the big blind on preflop, the seat after the button postflop). The amounts are reconstructed by replaying each round with the betting rules of the game, so they reflect the chips that actually moved.
| Column | Description | Example |
|---|---|---|
| TournID | Tournament in which the action happened | 2928882649 |
| HandID | Hand in which the action happened | 215024616736 |
| LocalTime | Time when the hand was played | 2020-06-07 07:52:12 |
| TableSize | Maximum number of players at the table | 9 |
| Playing | Number of players active in the hand | 6 |
| Level | Level of the tournament, as an integer | 15 |
| Ante | Ante of the hand | 4.0 |
| SmallBlind | Nominal small blind of the hand | 15.0 |
| BigBlind | Nominal big blind of the hand | 30.0 |
| Round | Round of the action (preflop, flop, turn, river) | preflop |
| Player | Player who acted | playername |
| Seat | Seat number of the player | 4 |
| Position | Position of the player (button, small blind, big blind), when any | big blind |
| Stack | Stack of the player right after the event (starting stack minus everything pushed so far) | 2340.0 |
| PostedAnte | Ante posted by the player in the hand (partial when all-in) | 4.0 |
| PostedBlind | Blind posted by the player in the hand (partial when all-in) | 30.0 |
| Action | The event (posts ante, posts small/big blind, folds, checks, calls, bets, raises) | raises |
| ActionIndex | Order of the action among the player's actions in the round (0 for posts) | 1 |
| ActionOrder | Chronological sequence of the action inside the hand (1..n) | 3 |
| AddedValue | Exact chips pushed by the action | 50.0 |
| TotalValue | Total put in by the player in the round after the action (on preflop includes the posted ante/blind) | 64.0 |
| TotalPot | Total pot right after the action (uncalled bets returned at the end are not discounted) | 156.0 |
| BoardC1..C5 | Board visible at the moment of the action (empty on preflop, 3 cards on flop, 4 on turn, 5 on river) | 4d, Tc, 7s |
| OwnerC1..C2 | Hole cards of the owner of the logs | Ah, Qd |
dim_tourn_summary
One row per tournament.
| Column | Description | Example |
|---|---|---|
| TournID | Unique identifier of the tournament | 2928882649 |
| LocalStartTime | Time of the first hand | 2020-06-07 07:44:35 |
| Modality | The type of game being played | USD Hold'em No Limit |
| BuyIn | The buy-in of the tournament | $4.60+$0.40 |
| Owner | Owner of the hand history files | ownername |
dim_player_summary
One row per player in each hand, holding the outcome: the result, the amount collected and the cards revealed at showdown (also for the losers, useful for range studies — null when the player did not reveal them).
| Column | Description | Example |
|---|---|---|
| TournID | Tournament of the hand | 2928882649 |
| HandID | Hand in which the player participated | 215024616736 |
| Player | Name of the player | playername |
| Result | Result of the hand (folded, lost, mucked, non-sd win, won) | won |
| Balance | Amount collected from the pot by the player in the hand (null when nothing was collected; the winners' amounts sum to the pot) | 840.0 |
| ShowDownC1..C2 | Cards revealed by the player at showdown | Ah, Ac |
| PokerHand | Card combination shown by the player | a pair of Aces |
dim_final_rank
One row per player in each tournament.
| Column | Description | Example |
|---|---|---|
| TournID | Tournament played | 2928882649 |
| Player | Name of the player | playername |
| FinalRank | Final rank in the tournament (0 when finished without a reported place, -1 when not registered in the logs) | 27 |
| Prize | Prize received, when any | 0.24 |
License
MIT Licence
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pokerdf-1.3.1.tar.gz.
File metadata
- Download URL: pokerdf-1.3.1.tar.gz
- Upload date:
- Size: 29.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
153d83761ee44f7331c66ff28e854de890e0465a4f19e5a834fb7bfbd1878e49
|
|
| MD5 |
a5c3161b3cf604b5297bf4883621c48f
|
|
| BLAKE2b-256 |
85883f0181795a5c53cbed417d3978e65691dfa067fe70b95b5f056fd8c17292
|
Provenance
The following attestation bundles were made for pokerdf-1.3.1.tar.gz:
Publisher:
python-publish.yml on murilogmamaral/pokerdf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pokerdf-1.3.1.tar.gz -
Subject digest:
153d83761ee44f7331c66ff28e854de890e0465a4f19e5a834fb7bfbd1878e49 - Sigstore transparency entry: 2373017317
- Sigstore integration time:
-
Permalink:
murilogmamaral/pokerdf@d0221a3c0f2afd7422e4c349071db41342716362 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/murilogmamaral
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@d0221a3c0f2afd7422e4c349071db41342716362 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pokerdf-1.3.1-py3-none-any.whl.
File metadata
- Download URL: pokerdf-1.3.1-py3-none-any.whl
- Upload date:
- Size: 31.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4110edd98402fc7c23cf80b2a371fb13ab4e99a1c29eb68fd9b56bb52d1f0d51
|
|
| MD5 |
745ecf66dd0781c48d37b09cfa8ccca1
|
|
| BLAKE2b-256 |
99df6c249cd366f8e00398907127256de1c94d1034437b38917fd1679506bd47
|
Provenance
The following attestation bundles were made for pokerdf-1.3.1-py3-none-any.whl:
Publisher:
python-publish.yml on murilogmamaral/pokerdf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pokerdf-1.3.1-py3-none-any.whl -
Subject digest:
4110edd98402fc7c23cf80b2a371fb13ab4e99a1c29eb68fd9b56bb52d1f0d51 - Sigstore transparency entry: 2373017346
- Sigstore integration time:
-
Permalink:
murilogmamaral/pokerdf@d0221a3c0f2afd7422e4c349071db41342716362 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/murilogmamaral
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@d0221a3c0f2afd7422e4c349071db41342716362 -
Trigger Event:
workflow_dispatch
-
Statement type: