Indian Lok Sabha Redistribution (850 Total Seats)

Political Economy
Author

Avinash Mani Tripathi

Published

April 15, 2026

A special session of parliament has been convened to discuss Constitutional Amendments for implementing wowen’s reservation in parliament. The proposed amendements, among other things, seek to modify the maximum number of Lok Sabha seats and adjust inter-state distribution seats as well. In order to understand the implications of seat adjustment exercise, this analysis focuses on redistributing seats across India based on the 2011 Census population data.

Objectives:

  1. Total Seats: 850 (States: 815, Union Territories: 35).
  2. States Allocation: 815 seats distributed among States.
  3. UTs Allocation: 35 seats distributed among Union Territories.
  4. No Decrease Rule: No state or UT will receive fewer seats than its current strength.
  5. Method: Proportional allocation (Largest Remainder Method) within each group (States and UTs).
  6. Relative Strength: Compare the percentage of the House each entity holds before and after redistribution.

Data

For each state and union territory– current seats in Lok Sabha, population as per 2011 census and a boolean variable flagging UT status–are recorded.

import pandas as pd
import numpy as np
import json

raw_data = {
    "Uttar Pradesh": {
        "pop": 199812341,
        "current": 80,
        "is_ut": False
    },
    "Maharashtra": {
        "pop": 112374333,
        "current": 48,
        "is_ut": False
    },
    "Bihar": {
        "pop": 104099452,
        "current": 40,
        "is_ut": False
    },
    "West Bengal": {
        "pop": 91276115,
        "current": 42,
        "is_ut": False
    },
    "Madhya Pradesh": {
        "pop": 72626809,
        "current": 29,
        "is_ut": False
    },
    "Tamil Nadu": {
        "pop": 72447030,
        "current": 39,
        "is_ut": False
    },
    "Rajasthan": {
        "pop": 68548473,
        "current": 25,
        "is_ut": False
    },
    "Karnataka": {
        "pop": 61095297,
        "current": 28,
        "is_ut": False
    },
    "Gujarat": {
        "pop": 60439692,
        "current": 26,
        "is_ut": False
    },
    "Andhra Pradesh": {
        "pop": 49577103,
        "current": 25,
        "is_ut": False
    },
    "Odisha": {
        "pop": 41974218,
        "current": 21,
        "is_ut": False
    },
    "Telangana": {
        "pop": 35003674,
        "current": 17,
        "is_ut": False
    },
    "Kerala": {
        "pop": 33406061,
        "current": 20,
        "is_ut": False
    },
    "Jharkhand": {
        "pop": 32988134,
        "current": 14,
        "is_ut": False
    },
    "Assam": {
        "pop": 31205576,
        "current": 14,
        "is_ut": False
    },
    "Punjab": {
        "pop": 27743338,
        "current": 13,
        "is_ut": False
    },
    "Chhattisgarh": {
        "pop": 25545198,
        "current": 11,
        "is_ut": False
    },
    "Haryana": {
        "pop": 25351462,
        "current": 10,
        "is_ut": False
    },
    "Uttarakhand": {
        "pop": 10086292,
        "current": 5,
        "is_ut": False
    },
    "Himachal Pradesh": {
        "pop": 6864602,
        "current": 4,
        "is_ut": False
    },
    "Tripura": {
        "pop": 3673917,
        "current": 2,
        "is_ut": False
    },
    "Meghalaya": {
        "pop": 2966889,
        "current": 2,
        "is_ut": False
    },
    "Manipur": {
        "pop": 2855794,
        "current": 2,
        "is_ut": False
    },
    "Nagaland": {
        "pop": 1978502,
        "current": 1,
        "is_ut": False
    },
    "Goa": {
        "pop": 1458545,
        "current": 2,
        "is_ut": False
    },
    "Arunachal Pradesh": {
        "pop": 1383727,
        "current": 2,
        "is_ut": False
    },
    "Mizoram": {
        "pop": 1097206,
        "current": 1,
        "is_ut": False
    },
    "Sikkim": {
        "pop": 610577,
        "current": 1,
        "is_ut": False
    },
    "Delhi": {
        "pop": 16787941,
        "current": 7,
        "is_ut": True
    },
    "Jammu & Kashmir": {
        "pop": 12267013,
        "current": 5,
        "is_ut": True
    },
    "Puducherry": {
        "pop": 1247953,
        "current": 1,
        "is_ut": True
    },
    "Chandigarh": {
        "pop": 1055450,
        "current": 1,
        "is_ut": True
    },
    "Dadra & Nagar Haveli and Daman & Diu": {
        "pop": 586956,
        "current": 2,
        "is_ut": True
    },
    "Andaman & Nicobar Islands": {
        "pop": 380581,
        "current": 1,
        "is_ut": True
    },
    "Ladakh": {
        "pop": 274289,
        "current": 1,
        "is_ut": True
    },
    "Lakshadweep": {
        "pop": 64473,
        "current": 1,
        "is_ut": True
    }
}

df = pd.DataFrame.from_dict(raw_data, orient='index').reset_index()
df.columns = ['Entity', 'Population', 'Current Seats', 'is_ut']

CURRENT_TOTAL_SEATS = 543
NEW_STATE_SEATS_TOTAL = 815
NEW_UT_SEATS_TOTAL = 35
NEW_HOUSE_TOTAL = NEW_STATE_SEATS_TOTAL + NEW_UT_SEATS_TOTAL

print(f"Current Strength: {CURRENT_TOTAL_SEATS}")
print(f"Target New Strength: {NEW_HOUSE_TOTAL} (States: {NEW_STATE_SEATS_TOTAL}, UTs: {NEW_UT_SEATS_TOTAL})")
Current Strength: 543
Target New Strength: 850 (States: 815, UTs: 35)

Allocation Logic for States and UTs

At one level the seat allocation is simple: One has to calculate the population proportion as per 2011 and allocate 815 seats to states and 35 seats to union territories in the same proportion. However, a staightforward application of this logic will create two complications: resulting seats will be fractional; and seats of some states may decrease in the exercise. Both are unrealistic.

For converting floats to integers, one can use functions like floor, ceiling or round (nearest integer). Rounding off using these methods is not guaranteed to result in sum of seats being equal to 815 and 35 for states and UTs however. If we use floor for rounding off, we will be left with surplus seats. These surplus seats can be allocated to the states in the order of remainder. This is called ‘Largest Remainder Method’. We define a function for the Largest Remainder Method with the ‘No Decrease’ constraint.

def allocate_seats(sub_df, target_total):
    total_pop = sub_df['Population'].sum()
    quota = total_pop / target_total
    
    sub_df = sub_df.copy()
    sub_df['Exact Share'] = sub_df['Population'] / quota
    sub_df['Base Seats'] = sub_df['Exact Share'].apply(lambda x: max(1, int(x)))
    sub_df['Remainder'] = sub_df['Exact Share'] - sub_df['Base Seats']
    
    sub_df['New Seats'] = sub_df.apply(lambda x: max(x['Base Seats'], x['Current Seats']), axis=1)
    
    current_total = sub_df['New Seats'].sum()
    surplus = target_total - current_total
    
    if surplus > 0:
        top_remainders = sub_df.sort_values(by='Remainder', ascending=False).index[:surplus]
        sub_df.loc[top_remainders, 'New Seats'] += 1
    elif surplus < 0:
        can_reduce = sub_df[sub_df['New Seats'] > sub_df.apply(lambda x: max(x['Current Seats'], 1), axis=1)].index
        to_reduce = sub_df.loc[can_reduce].sort_values(by='Remainder').index[:abs(surplus)]
        sub_df.loc[to_reduce, 'New Seats'] -= 1
        
    return sub_df

states_df = allocate_seats(df[~df['is_ut']], NEW_STATE_SEATS_TOTAL)
uts_df = allocate_seats(df[df['is_ut']], NEW_UT_SEATS_TOTAL)

final_df = pd.concat([states_df, uts_df])
print(f"Total State Seats: {states_df['New Seats'].sum()}")
print(f"Total UT Seats: {uts_df['New Seats'].sum()}")
print(f"Total House Seats: {final_df['New Seats'].sum()}")
Total State Seats: 815
Total UT Seats: 35
Total House Seats: 850

Final Results and Relative Strength Comparison

For comparision, we define relative stregth change. Gainers and losers in this exercise are highlighted using pandas conditional formatting.

Relative Strength Change = (New Seats / 850 * 100) - (Current Seats / 543 * 100). Highlighted in green (increase) or red (decrease).

final_df['Current %'] = (final_df['Current Seats'] / CURRENT_TOTAL_SEATS * 100).round(4)
final_df['New %'] = (final_df['New Seats'] / NEW_HOUSE_TOTAL * 100).round(4)
final_df['Rel. Strength Change'] = (final_df['New %'] - final_df['Current %']).round(4)

def highlight_change(val):
    if val > 0.005:
        return 'color: green; font-weight: bold'
    elif val < -0.005:
        return 'color: red; font-weight: bold'
    return ''

display_df = final_df[['Entity', 'Population', 'Current Seats', 'New Seats', 'Current %', 'New %', 'Rel. Strength Change']]
display_df = display_df.sort_values(by=['New Seats', 'Population'], ascending=False)

styled_df = display_df.style.map(highlight_change, subset=['Rel. Strength Change'])
styled_df
  Entity Population Current Seats New Seats Current % New % Rel. Strength Change
0 Uttar Pradesh 199812341 80 138 14.733000 16.235300 1.502300
1 Maharashtra 112374333 48 78 8.839800 9.176500 0.336700
2 Bihar 104099452 40 72 7.366500 8.470600 1.104100
3 West Bengal 91276115 42 63 7.734800 7.411800 -0.323000
4 Madhya Pradesh 72626809 29 50 5.340700 5.882400 0.541700
5 Tamil Nadu 72447030 39 50 7.182300 5.882400 -1.299900
6 Rajasthan 68548473 25 47 4.604100 5.529400 0.925300
7 Karnataka 61095297 28 42 5.156500 4.941200 -0.215300
8 Gujarat 60439692 26 42 4.788200 4.941200 0.153000
9 Andhra Pradesh 49577103 25 34 4.604100 4.000000 -0.604100
10 Odisha 41974218 21 29 3.867400 3.411800 -0.455600
11 Telangana 35003674 17 24 3.130800 2.823500 -0.307300
12 Kerala 33406061 20 23 3.683200 2.705900 -0.977300
13 Jharkhand 32988134 14 23 2.578300 2.705900 0.127600
14 Assam 31205576 14 21 2.578300 2.470600 -0.107700
15 Punjab 27743338 13 19 2.394100 2.235300 -0.158800
16 Chhattisgarh 25545198 11 17 2.025800 2.000000 -0.025800
17 Haryana 25351462 10 17 1.841600 2.000000 0.158400
28 Delhi 16787941 7 16 1.289100 1.882400 0.593300
29 Jammu & Kashmir 12267013 5 12 0.920800 1.411800 0.491000
18 Uttarakhand 10086292 5 7 0.920800 0.823500 -0.097300
19 Himachal Pradesh 6864602 4 5 0.736600 0.588200 -0.148400
22 Manipur 2855794 2 3 0.368300 0.352900 -0.015400
20 Tripura 3673917 2 2 0.368300 0.235300 -0.133000
21 Meghalaya 2966889 2 2 0.368300 0.235300 -0.133000
24 Goa 1458545 2 2 0.368300 0.235300 -0.133000
25 Arunachal Pradesh 1383727 2 2 0.368300 0.235300 -0.133000
32 Dadra & Nagar Haveli and Daman & Diu 586956 2 2 0.368300 0.235300 -0.133000
23 Nagaland 1978502 1 1 0.184200 0.117600 -0.066600
30 Puducherry 1247953 1 1 0.184200 0.117600 -0.066600
26 Mizoram 1097206 1 1 0.184200 0.117600 -0.066600
31 Chandigarh 1055450 1 1 0.184200 0.117600 -0.066600
27 Sikkim 610577 1 1 0.184200 0.117600 -0.066600
33 Andaman & Nicobar Islands 380581 1 1 0.184200 0.117600 -0.066600
34 Ladakh 274289 1 1 0.184200 0.117600 -0.066600
35 Lakshadweep 64473 1 1 0.184200 0.117600 -0.066600

Final Diagnostics and Tests

It is never a good idea to trust your calculations without validation. So we throw in some tests here, asserting that total allocated seats are equal to 850; total allocated seats to UTs are equal to 35; percentages sum to 100: and gains and losses net out. Our estimates pass all the tests, validating the analysis.

print("### Final Diagnostics")
curr_total = final_df['Current Seats'].sum()
curr_ut_total = final_df.query('is_ut')['New Seats'].sum()
new_total = final_df['New Seats'].sum()
pct_total = final_df['New %'].sum()
rel_change_total = final_df['Rel. Strength Change'].sum()

print(f"Total Current Seats: {curr_total} (Expected: 543)")
print(f"Total New Seats (Total): {new_total} (Expected: 850)")
print(f"Total New Seats (UT): {curr_ut_total} (Expected: 35)")
print(f"Total New %: {pct_total:.2f}% (Expected: 100.00%)")
print(f"Total Relative Change: {rel_change_total:.4f} (Expected: 0.0000)")

assert curr_total == 543
assert new_total == 850
assert curr_ut_total == 35
assert abs(pct_total - 100.0) < 0.1
assert abs(rel_change_total) < 0.1
### Final Diagnostics
Total Current Seats: 543 (Expected: 543)
Total New Seats (Total): 850 (Expected: 850)
Total New Seats (UT): 35 (Expected: 35)
Total New %: 100.00% (Expected: 100.00%)
Total Relative Change: -0.0003 (Expected: 0.0000)