NFIP Claim Totals Look Wrong: Negative Payments and Impossible Water Depths

On this page

Problem statement

Summing NFIP claim payments, averaging flood depths or mapping claim locations from OpenFEMA gives numbers that look slightly off, and the reasons are in the values themselves. Some payments are negative, down to βˆ’$2,250,000. Water depth is documented as inches, yet its median is 1 and values of 1–4 come with damage similar to 12–48 inches, because some depths were entered in feet. There are thousands of negative depths, a cluster at 99, and coordinates that are deliberately coarse. This page finds each problem in the claims since 2017 and gives a cleaning function.

Quick answer

Clip netBuildingPaymentAmount at zero when summing what was paid. Treat waterDepth outside a plausible range (negative, or several hundred) as missing, and do not use the small values as inches without checking them against damage. Aggregate by countyCode, not by the rounded coordinates.

neg = nfip[nfip.netBuildingPaymentAmount < 0]
print(f"negative building payments: {len(neg)}, totalling ${neg.netBuildingPaymentAmount.sum() / 1e6:,.1f} million; most negative ${neg.netBuildingPaymentAmount.min():,.0f}")
print(f"water depth: median {nfip.waterDepth.median():.0f}, share 0-6: {nfip.waterDepth.between(0, 6).mean():.1%}, "
      f"negative {(nfip.waterDepth < 0).sum():,}, 99 {(nfip.waterDepth == 99).sum():,}, 900+ {(nfip.waterDepth >= 900).sum()}")
negative building payments: 84, totalling $-5.5 million; most negative $-2,250,000
water depth: median 1, share 0-6: 77.0%, negative 16,202, 99 13,539, 900+ 3

84 claims have negative building payments, totalling βˆ’$5.5 million, the largest βˆ’$2,250,000. Water depth has a median of 1, and 77.0% of recorded depths are between 0 and 6. There are 16,202 negative depths, 13,539 depths of exactly 99 and 3 of 900 or more.

Histogram on a log scale of recorded waterDepth values up to ten feet in inches: tall bars at 0 to 4, a declining tail, and a red spike at 99. Histogram on a log scale of recorded waterDepth values up to ten feet in inches: tall bars at 0 to 4, a declining tail, and a red spike at 99.
Recorded water depths: most are 0–4, with a spike of 13,539 at 99.

Payments, depths and positions

1. Net payments subtract recoveries

print(neg[["dateOfLoss", "state", "floodEvent", "buildingDamageAmount", "netBuildingPaymentAmount"]].sort_values("netBuildingPaymentAmount").head(4).to_string(index=False))
print(f"sum with negatives ${nfip.netBuildingPaymentAmount.sum() / 1e9:.3f} billion; with negatives set to 0 ${nfip.netBuildingPaymentAmount.clip(lower=0).sum() / 1e9:.3f} billion")
               dateOfLoss state       floodEvent  buildingDamageAmount  netBuildingPaymentAmount
2022-09-28 00:00:00+00:00    FL    Hurricane Ian              258969.0               -2250000.00
2024-09-26 00:00:00+00:00    FL Hurricane Helene                   1.0                -201667.50
2021-09-01 00:00:00+00:00    NY    Hurricane Ida              103404.0                -200000.00
2024-10-10 00:00:00+00:00    FL Hurricane Milton                   1.0                -179538.53
sum with negatives $27.245 billion; with negatives set to 0 $27.251 billion

Net payment is the amount paid minus recoveries, such as money returned after an overpayment, a duplicate claim or a subrogation recovery. A negative value is a claim that returned more than it was paid in this record. An Ian claim shows βˆ’$2,250,000 against $258,969 of damage, and two claims with $1 of damage show recoveries of about $200,000. They change the total little ($27.245 billion with them, $27.251 billion clipped), but they break averages and logarithmic scales.

2. Water depth is inches, except when it is feet

w = nfip.waterDepth.dropna()
print(w.value_counts().head(10).to_dict())
d = nfip.dropna(subset=["waterDepth", "buildingDamageAmount"])
d = d[d.buildingDamageAmount > 0]
sel = d[d.waterDepth.isin([1, 2, 3, 4, 12, 24, 36, 48])]
print("median building damage by recorded depth:", sel.groupby("waterDepth").buildingDamageAmount.median().round(-2).to_dict())
{0.0: 161435, 1.0: 122994, 2.0: 52141, 3.0: 25818, 4.0: 14432, 99.0: 13539, 5.0: 7960, 6.0: 5253, 7.0: 3475, 8.0: 3039}
median building damage by recorded depth: {1.0: 48600.0, 2.0: 69200.0, 3.0: 77300.0, 4.0: 81200.0, 12.0: 76000.0, 24.0: 87300.0, 36.0: 91300.0, 48.0: 95700.0}

Depths of 0 to 4 make up most of the values, and damage barely separates them from foot-scale depths: median building damage is $48,600 at a recorded 1, $81,200 at 4, $76,000 at 12 and $95,700 at 48. If 1–4 were inches, a few inches of water would cause as much damage as several feet. FEMA's field note says some measurements were given in feet, and these small values look like most of them. There is no flag to tell which, so treat depths below about 12 as unreliable.

Scatter plot of median building damage against recorded water depth: depth 0 lowest, depth 1 at $48,600, and depths 2 to 5 rising to the level of depths 12 to 24. Scatter plot of median building damage against recorded water depth: depth 0 lowest, depth 1 at $48,600, and depths 2 to 5 rising to the level of depths 12 to 24.
Median damage at recorded depths of 2–5 is as high as at 12–24, so many small values are feet.

3. Coordinates are deliberately coarse

print(f"latitudes on a 0.1-degree grid: {(nfip.latitude.dropna() * 10).round(6).mod(1).eq(0).mean():.1%}; missing: {nfip.latitude.isna().sum():,}")
print(f"claims per distinct position: median {nfip.groupby(['latitude', 'longitude']).size().median():.0f}, most {nfip.groupby(['latitude', 'longitude']).size().max():,}")
print("negative latitudes by state:", nfip[nfip.latitude < 0].state.value_counts().to_dict())
latitudes on a 0.1-degree grid: 100.0%; missing: 1,916
claims per distinct position: median 3, most 7,234
negative latitudes by state: {'AS': 5, 'NC': 1}

Every latitude is on a 0.1-degree grid, and 1,916 claims have no position. The median position carries 3 claims, but the busiest carries 7,234. The six negative latitudes are five in American Samoa (correct, south of the equator) and one North Carolina claim, which is a sign error.

A cleaning function

The data used on this page

FEMA's NFIP Redacted Claims (version 3) from OpenFEMA, every claim with a loss in 2017 or later, saved on this site on 15 October 2026.

from pathlib import Path

import geopandas as gpd
import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
# OpenFEMA, queried on 15 October 2026 and saved on this site because both datasets are revised continually:
# DisasterDeclarationsSummaries v2 (every declaration since 1953, one row per designated area) and NfipClaims v3
# (flood insurance claims with a loss in 2017 or later, 29 fields). The queries are in data/README.txt; the live API is
# https://www.fema.gov/api/open/v2/DisasterDeclarationsSummaries and https://www.fema.gov/api/open/v3/NfipClaims
FILES = ["fema-disaster-declarations-2026-10-15.zip", "nfip-claims-2017-2026-2026-10-15.zip"]
for name in FILES:
    if not Path(f"data/{name}").exists():
        Path(f"data/{name}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{name}", timeout=600).content)
dds = pd.read_csv("data/fema-disaster-declarations-2026-10-15.zip", dtype={"fipsStateCode": str, "fipsCountyCode": str, "placeCode": str},
                  parse_dates=["declarationDate", "incidentBeginDate", "incidentEndDate"])
nfip = pd.read_csv("data/nfip-claims-2017-2026-2026-10-15.zip", low_memory=False, dtype={"countyCode": str, "reportedZipCode": str},
                   parse_dates=["dateOfLoss"])
print(f"OpenFEMA: {len(dds):,} declaration rows for {dds.disasterNumber.nunique():,} declarations, "
      f"{dds.declarationDate.min():%Y}-{dds.declarationDate.max():%Y}")
OpenFEMA: 70,567 declaration rows for 5,281 declarations, 1953-2026

Example 1: a cleaning function

def clean(c):
    c = c.copy()
    c["paid_building"] = c.netBuildingPaymentAmount.clip(lower=0)
    c["depth_in"] = c.waterDepth.where(c.waterDepth.between(0, 600))
    return c


cl = clean(nfip)
print(f"depths kept: {cl.depth_in.notna().sum():,} of {nfip.waterDepth.notna().sum():,}; building paid ${cl.paid_building.sum() / 1e9:.3f} billion")
depths kept: 424,273 of 440,481; building paid $27.251 billion

The function keeps 424,273 of the 440,481 recorded depths that fall in the range it allows and gives a building total of $27.251 billion. It does not fix the inch-or-foot ambiguity, which no rule can resolve from the field alone, but it removes the values that are impossible either way.

Histogram on log-log axes of how many claims share one rounded position, from single claims to more than seven thousand. Histogram on log-log axes of how many claims share one rounded position, from single claims to more than seven thousand.
Claims per rounded position: the median position carries 3 claims, the busiest 7,234.

Example 2: zero is not missing

print(f"water depth exactly 0: {(nfip.waterDepth == 0).sum():,}; of those with building damage over $10,000: "
      f"{((nfip.waterDepth == 0) & (nfip.buildingDamageAmount > 10_000)).sum():,}")
water depth exactly 0: 161,435; of those with building damage over $10,000: 67,660

161,435 claims record a depth of exactly 0, and 67,660 of those had building damage over $10,000. A zero is often a placeholder for "not recorded", or water that came from below without standing in the building, so it is not evidence of a dry building. Do not average it with real depths.

Where the values come from

NFIP claims are filed by insurance companies (Write Your Own carriers) and FEMA's direct servicer, through the program's transaction reporting system, and adjusters enter many of the fields by hand. Fields that drive payment, such as damage, coverage and payment, are checked carefully. Descriptive fields such as water depth are not, which is how feet ended up in an inches field and why zero and 99 serve as placeholders. Recoveries are posted against the original claim and can drive the net below zero.

The redaction adds a third layer: positions rounded to 0.1Β° by design. Together they mean the payment fields are reliable to the dollar and the descriptive fields need checking before use.

Other fields to check

  • buildingDamageAmount of 1 appears on some recovery records; filter it out of damage statistics.
  • policyCount is more than 1 for condominium association policies; a claim can stand for many units.
  • causeOfDamage can combine codes (a number and a letter for remote adjustment); split before grouping.

FAQ

Why are some NFIP claim payments negative?

The net payment is the amount paid minus recoveries. When more was recovered than paid in the record, the net is negative; 84 building payments since 2017 are, down to βˆ’$2,250,000.

What unit is NFIP waterDepth in?

Inches, according to FEMA, but some depths were entered in feet. Values of 1–4 have damage similar to 12–48 inches, which suggests many of them are feet.