top of page
  • Matt

Decision Factors in Poker

Updated: Jun 12, 2020

"Fold and live to fold again"

-Stu Ungar

Poker players base decisions on a multitude of factors. In this analysis, we uncover (and rank) the most important factors for accurately predicting a player's decisions in Texas Hold 'Em. We can use this analysis to gain insight into poker player behavior and potentially reduce model training time through feature selection.

This analysis leverages a multi-class Random Forest model. The full code can be found on my GitHub.

Based on my analysis, the most important factors for determining poker player actions are the values of the player's two cards. Measuring the impact of dropping individual features proved to be an effective indicator for feature importance.

Data Sources

Through Bovada, I found data for over 30,000 preflop decisions in 6-handed $0.02/$0.05 no-limit Texas Hold 'Em games. After a rigorous ETL process, I identified 10 features to evaluate:

-Value 1 - The value of the player's first card (e.g. 2 = 2, Ace =14).

-Value 2 - The value of the player's second card.

-Player Suits - If the player's cards are the same suit, the value is 2. Otherwise, the value is 1.

-Amount to Call - The size of the bet the player is facing.

-Pot Size - The amount of money the player can win.

-Position - The location of the player relative to others (e.g. small blind is 0, dealer is 5).

-Active - The number of times players have voluntarily invested money in the hand.

-Invested Pre Action - The amount of money the player has already invested in the hand.

-Starting Stack - The amount of money the player started the hand with.

-Game ID - The unique game identification number.

There are three distinct classes (labels) in this model.

-Action_raises - The player raises at the given decision point.

-Action_calls - The player calls (or checks).

-Action_folds - The player folds.


For initial data explorations, I developed a heatmap of correlations between the 10 features and the 3 labels (i.e. likelihood to fold, call, or raise). Heatmaps enable us to quickly identify which features are strongly correlated with our desired label. As a personal preference, I find the triangular heatmaps easier to interpret.

# Computing the correlation matrix with a triangular mask
viz = hand_history[viz_components]
corr = viz.corr()
mask = np.triu(np.ones_like(corr, dtype=np.bool))
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(corr, mask=mask, cmap=cmap)

The heatmap revealed three interesting sets of correlations.

1) "Value 1" and "Value 2" are positively correlated with the label "Action_raises". Obviously if the player has higher cards, they are more likely to raise. Conversely, "Value 1" and "Value 2" are negatively correlated with the label "Action_folds". High cards reduce the likelihood that a player will fold.

2) "Active" is positively correlated with "Action_calls". "Active" measures the number of players that have voluntarily invested in the pot already. If the hand has more players, there is a higher likelihood that the player would "call" to join the action.

3) "Pot Size", "Active", "Invested Pre Action", and "Amount to Call" are all positively correlated with each other. If you have already "Invested Pre Action", you have inflated the "Pot Size" with your bet. The "Pot Size" is likely to grow if there are more "Active" players. Additionally if multiple "Active" players raise, the "Amount to Call" will increase with each additional raise.

The bulk of this analysis leverages Random Forest models. Random Forest models combine the results from several decision trees to make predictions about target variables. I selected the Random Forest algorithm because the model reliably estimates feature importance.

# This function generates a Random Forest with 100 estimators
def random_forest_processor(data):
    x_train, x_valid, y_train, y_valid = train_test_split(data, labels, test_size=0.8)
    rf = RandomForestClassifier(n_estimators=100), y_train)
    print(rf.score(x_valid, y_valid))
    return rf.score(x_train, y_train), rf.score(x_valid, y_valid), rf

The Random Forest method in Python contains a "feature_importances_" attribute. By default, the "feature_importances_" attribute relies on the Gini Importance measure. Gini Importance is the probability-weighted average decrease in node impurity. Basically, Gini Importance reflects the amount of information gained from a given feature.

# This code block evaluates then plots Gini Importance scores
base_train_acc, base_test_acc, model = random_forest_processor(hand_history[features_pre])
dfscores = pd.DataFrame(model.feature_importances_)
dfcolumns = pd.DataFrame(hand_history[features_pre].columns)
featureScores = pd.concat([dfcolumns, dfscores], axis=1)
featureScores.columns = ['Feature', 'Gini Importance']
featureScores = featureScores.sort_values(by=['Gini Importance'])
y_pos = np.arange(len(featureScores['Feature']))
plt.barh(y_pos, featureScores['Gini Importance'])

"Value 2" and "Value 1" have the highest Gini Importance scores and seem to be the most important factors for accurately predicting a player's decisions. Surprisingly, the Gini Importance scores for "Starting Stack" and the randomly-generated "Game ID" have relatively high scores and indicate some importance within the model. To explore the impact of these features, we investigate a dropping features algorithm.

Dropping features is a common method for isolating individual feature impacts. First, we set a baseline test set accuracy with the entire set of columns (90.6% in this case). Then, we systemically drop one feature at a time and rebuild the model. By comparing the accuracy of the reduced model against our baseline, we can determine the importance of each feature.

# This code block compares reduced models against the base case
for col in features_pre:
    data = hand_history[features_pre].drop(columns=col)
    train_acc, test_acc, model = random_forest_processor(data)
    instance = {"Feature": col, "Change in Accuracy": test_acc-base_test_acc}
    error_tracker = error_tracker.append(instance, ignore_index=True)
error_tracker = error_tracker.sort_values(by=['Change in Accuracy'])

Dropping "Value 1" and "Value 2" from the model have a significantly negative impact on accuracy. This finding is aligned with the output from the Gini Importance measure. Unlike the Gini Importance measure, the dropping columns algorithm shows that "Game ID" and "Starting Stack" do not have a meaningful impact on model accuracy. Perhaps several combinations of "Stacking Stack" and "Game ID" correlated to individual players with predictable playing styles.

Conclusions and Potential Next Steps

Dropping columns in the Random Forest algorithm enabled us to determine that the values of a player's two cards are the most important features influencing player's decisions.

Additional research ideas:

-Compare the prediction accuracy of the Random Forest model against neural networks

-Further investigate the relatively high Gini Importance scores for the "Starting Stack" and "Game ID" features

-Experiment with permutation feature importance

-Evaluate the dataset's accuracy using local interpretable model-agnostic explanations (LIME)

114 views1 comment

Recent Posts

See All
Post: Blog2_Post
bottom of page