2026 House Forecast

2026 Elections
Forecasts
Who will win the House in November?
Published

September 1, 2026

Modified

October 18, 2026

See all 2026 midterms forecasts: House / Senate / House methodology / Senate methodology

This page hosts the 2026 SnoutCounter House forecast. A blue wave in the House is widely expected as Trump’s approval continues to crater and sentiment on the economy and the direction of the country worsens. Despite extensive mid-decade redistricting, which has generally benefited Republicans, Democrats are still favored to win the House.

You can read about how the model that powers this forecast works here. The source code is publicly available on GitHub.

Below you can find a map of seat chances and projections, as well as some overall summary stats. Note: Datawrapper’s map still uses the 2026 gerrymander for Missouri, but that map has since been struck down and replaced with the old map used for 2022 and 2024.

The histogram below displays seat outcomes of all 20,000 simulations drawn from the model’s posterior distribution.

Below you can find a table displaying the output of the House model, including some additional information not shown above.

And below, you can find time series charts showing how the forecast has changed over time.

Additionally, you can see how the forecasted odds and vote shares have changed for each individual congressional seat below.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 600px
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
from shiny import *
from shiny.express import input, render, ui
from shinywidgets import render_widget

output_ts = pd.read_csv('https://raw.githubusercontent.com/Hackquantumcpp/snoutcounter/refs/heads/main/datasets/house_forecast_2026_output_over_time.csv')

output_ts = output_ts[output_ts['type'].isin(['chance', 'y_pred', 'ci_low', 'ci_hi'])]

output_ts = output_ts[output_ts['geo'] != 'US House']

output_ts['date'] = pd.to_datetime(output_ts['date'])

seats = dict(zip(output_ts['geo'], output_ts['geo']))


ui.input_selectize(
    "cd",
    "Congressional District:",
    seats
  )
ui.input_radio_buttons(
    "type_disp",
    "",
    {"chance": "Win probability", "y_pred": "Vote share"}
  )


@render_widget
def plot():
  disp_output = output_ts[(output_ts['geo'] == input.cd()) & (output_ts['type'] == input.type_disp())]
  ci_output = output_ts[(output_ts['geo'] == input.cd()) & (output_ts['type'].isin(['ci_low', 'ci_hi']))]
  disp_output['Republicans'] = disp_output['y'].map(lambda x: 100 - x)
  disp_output = disp_output.rename({'y': 'Democrats'}, axis=1)
    
  disp_yaxis = "Win Probability" if input.type_disp() == "chance" else "Vote share"
    
  chart = px.line(disp_output, x='date', y=['Democrats', 'Republicans'], template='plotly_white', color_discrete_map={'Democrats': '#004b97', 'Republicans': '#c71e1d'})
  chart.update_traces(hovertemplate="%{y:.1f}%")
  chart.update_layout(
      xaxis=dict(range=[pd.to_datetime('2026-08-30'), pd.to_datetime('2026-11-10')]),
      yaxis=dict(range=[0, 100]),
      xaxis_title='Date',
      yaxis_title=f'{disp_yaxis} (%)',
      title=dict(text=f"{input.cd()} {disp_yaxis} Over Time"),
      hovermode="x",
      showlegend=False
  )
  if input.type_disp() == "y_pred":
    chart.add_traces(
      [go.Scatter(
        name = 'dem ci hi',
        x = ci_output[ci_output['type'] == 'ci_hi']['date'],
        y = ci_output[ci_output['type'] == 'ci_hi']['y'],
        mode = 'lines',
        marker = dict(color='#ffffff'),
        line = dict(width=0),
        showlegend = False,
        hoverinfo = 'skip'
      ),
      go.Scatter(
        name = 'dem ci low',
        x = ci_output[ci_output['type'] == 'ci_low']['date'],
        y = ci_output[ci_output['type'] == 'ci_low']['y'],
        mode = 'lines',
        marker = dict(color='#ffffff'),
        line = dict(width=0),
        showlegend = False,
        fillcolor = "rgba(87, 158, 230, 0.3)",
        fill = 'tonexty',
        hoverinfo = 'skip'
      )
      ]
    )
    chart.add_traces(
      [go.Scatter(
        name = 'rep ci hi',
        x = ci_output[ci_output['type'] == 'ci_hi']['date'],
        y = ci_output[ci_output['type'] == 'ci_hi']['y'].map(lambda x: 100 - x),
        mode = 'lines',
        marker = dict(color='#ffffff'),
        line = dict(width=0),
        showlegend = False,
        hoverinfo = 'skip'
      ),
      go.Scatter(
        name = 'rep ci low',
        x = ci_output[ci_output['type'] == 'ci_low']['date'],
        y = ci_output[ci_output['type'] == 'ci_low']['y'].map(lambda x: 100 - x),
        mode = 'lines',
        marker = dict(color='#ffffff'),
        line = dict(width=0),
        showlegend = False,
        fillcolor = "rgba(245, 105, 105, 0.3)",
        fill = 'tonexty',
        hoverinfo = 'skip'
      )
      ]
    )
    
  return chart