585. Investments in 2016
On LeetCode ->Problem¶
Given Insurance, return the rounded sum of tiv_2016 for policies where:
tiv_2015appears in at least two rows.- The location pair
(lat, lon)appears in exactly one row.
Input:
# Insurance
----+----------+----------+-----+----
pid | tiv_2015 | tiv_2016 | lat | lon
----+----------+----------+-----+----
1 | 10 | 5 | 10 | 10
2 | 20 | 20 | 20 | 20
3 | 10 | 30 | 20 | 20
4 | 10 | 40 | 40 | 40
Output:
Key trick¶
Use group counts as row-level filters:
- Count rows per
tiv_2015; keep rows with count greater than1. - Count rows per
(lat, lon); keep rows with count equal to1.
Trap¶
- Checking
latandlonuniqueness separately instead of checking the pair(lat, lon). - Rounding each
tiv_2016before summing instead of rounding the final sum. - Joining grouped tables incorrectly and duplicating rows.
- In PostgreSQL,
ROUND(double precision, integer)is not valid, so the sum must be cast tonumeric.
Why is it interesting?¶
It tests whether you can combine aggregate conditions with row-level filtering, which is exactly what window functions or groupby().transform() are good at.
SQL solution¶
SQLite¶
WITH marked AS (
SELECT
tiv_2016,
-- Same 2015 investment value as at least one other policy.
COUNT(*) OVER (
PARTITION BY tiv_2015
) AS tiv_2015_count,
-- Unique city means the full coordinate pair appears once.
COUNT(*) OVER (
PARTITION BY lat, lon
) AS location_count
FROM Insurance
)
SELECT
-- Round only the final sum.
ROUND(SUM(tiv_2016), 2) AS tiv_2016
FROM marked
WHERE tiv_2015_count > 1
AND location_count = 1;
PostgreSQL¶
WITH marked AS (
SELECT
tiv_2016,
-- Same 2015 investment value as at least one other policy.
COUNT(*) OVER (
PARTITION BY tiv_2015
) AS tiv_2015_count,
-- Unique city means the full coordinate pair appears once.
COUNT(*) OVER (
PARTITION BY lat, lon
) AS location_count
FROM Insurance
)
SELECT
-- PostgreSQL needs numeric for ROUND(value, decimals).
ROUND(SUM(tiv_2016)::numeric, 2) AS tiv_2016
FROM marked
WHERE tiv_2015_count > 1
AND location_count = 1;
Pandas solution¶
import pandas as pd
def find_investments(insurance: pd.DataFrame) -> pd.DataFrame:
marked = insurance.assign(
# Broadcast each tiv_2015 group size back to its original rows.
tiv_2015_count=insurance.groupby("tiv_2015")["pid"].transform("size"),
# Broadcast each location-pair group size back to its original rows.
location_count=insurance.groupby(["lat", "lon"])["pid"].transform("size"),
)
valid_rows = marked[
(marked["tiv_2015_count"] > 1)
& (marked["location_count"] == 1)
]
# min_count=1 matches SQL SUM behavior when no rows qualify.
total = valid_rows["tiv_2016"].sum(min_count=1)
if pd.notna(total):
total = round(float(total), 2)
else:
total = pd.NA
return pd.DataFrame({"tiv_2016": [total]})
data = [[1, 10, 5, 10, 10], [2, 20, 20, 20, 20], [3, 10, 30, 20, 20], [4, 10, 40, 40, 40]]
insurance = pd.DataFrame(data, columns=['pid', 'tiv_2015', 'tiv_2016', 'lat', 'lon']).astype({'pid':'Int64', 'tiv_2015':'Float64', 'tiv_2016':'Float64', 'lat':'Float64', 'lon':'Float64'})
insurance
# pid tiv_2015 tiv_2016 lat lon
# 0 1 10.0 5.0 10.0 10.0
# 1 2 20.0 20.0 20.0 20.0
# 2 3 10.0 30.0 20.0 20.0
# 3 4 10.0 40.0 40.0 40.0
# Broadcast each tiv_2015 group size back to its original rows.
insurance.assign(
tiv_2015_count=insurance.groupby("tiv_2015")["pid"].transform("size")
)
# pid tiv_2015 tiv_2016 lat lon tiv_2015_count
# 0 1 10.0 5.0 10.0 10.0 3
# 1 2 20.0 20.0 20.0 20.0 1
# 2 3 10.0 30.0 20.0 20.0 3
# 3 4 10.0 40.0 40.0 40.0 3
insurance.groupby("tiv_2015")["pid"].transform("size")
# 0 3
# 1 1
# 2 3
# 3 3
# Name: pid, dtype: Int64
insurance.groupby("tiv_2015")["pid"].size()
# tiv_2015
# 10.0 3
# 20.0 1
# Name: pid, dtype: Int64
Pytest test¶
import sqlite3
import pandas as pd
import pytest
SQLITE_QUERY = """
WITH marked AS (
SELECT
tiv_2016,
COUNT(*) OVER (
PARTITION BY tiv_2015
) AS tiv_2015_count,
COUNT(*) OVER (
PARTITION BY lat, lon
) AS location_count
FROM Insurance
)
SELECT
ROUND(SUM(tiv_2016), 2) AS tiv_2016
FROM marked
WHERE tiv_2015_count > 1
AND location_count = 1;
"""
def pandas_solution(insurance: pd.DataFrame) -> pd.DataFrame:
marked = insurance.assign(
tiv_2015_count=insurance.groupby("tiv_2015")["pid"].transform("size"),
location_count=insurance.groupby(["lat", "lon"])["pid"].transform("size"),
)
valid_rows = marked[
(marked["tiv_2015_count"] > 1)
& (marked["location_count"] == 1)
]
total = valid_rows["tiv_2016"].sum(min_count=1)
if pd.notna(total):
total = round(float(total), 2)
else:
total = pd.NA
return pd.DataFrame({"tiv_2016": [total]})
@pytest.mark.parametrize(
"rows, expected",
[
(
[
[1, 10.0, 5.0, 10.0, 10.0],
[2, 20.0, 20.0, 20.0, 20.0],
[3, 10.0, 30.0, 20.0, 20.0],
[4, 10.0, 40.0, 40.0, 40.0],
],
45.00,
),
(
[
[1, 1.0, 1.111, 0.0, 0.0],
[2, 1.0, 2.225, 1.0, 1.0],
],
3.34,
),
(
[
[1, 10.0, 5.0, 0.0, 0.0],
[2, 10.0, 6.0, 0.0, 0.0],
[3, 20.0, 7.0, 2.0, 2.0],
[4, 30.0, 8.0, 3.0, 3.0],
[5, 30.0, 9.0, 4.0, 4.0],
],
17.00,
),
(
[
[1, 10.0, 5.0, 1.0, 1.0],
[2, 20.0, 6.0, 2.0, 2.0],
],
None,
),
],
)
def test_investments_sqlite_and_pandas(rows, expected):
columns = ["pid", "tiv_2015", "tiv_2016", "lat", "lon"]
insurance = pd.DataFrame(rows, columns=columns)
with sqlite3.connect(":memory:") as connection:
insurance.to_sql(
"Insurance",
connection,
index=False,
if_exists="replace",
)
sql_value = pd.read_sql_query(SQLITE_QUERY, connection).iloc[0, 0]
pandas_value = pandas_solution(insurance).iloc[0, 0]
if expected is None:
assert pd.isna(sql_value)
assert pd.isna(pandas_value)
else:
assert round(float(sql_value), 2) == pytest.approx(expected)
assert round(float(pandas_value), 2) == pytest.approx(expected)
Comment on my solution¶
Your working SQL solution is correct, but it is PostgreSQL-specific because of the ::numeric cast.
Your working Pandas solution is correct, but the merge can be avoided with groupby().transform("size"), which is usually cleaner for row-level filters based on group counts.
The failing Pandas version breaks because isin() with a MultiIndex does not compare row pairs from insurance[["lat", "lon"]] the way you intend; use a merge, a tuple key, or groupby().transform() instead.
-- WORKS
WITH valid_pids AS (
SELECT pid
FROM (
SELECT
pid,
COUNT(*) OVER (PARTITION BY tiv_2015) AS cnt_tiv_2015,
COUNT(*) OVER (PARTITION BY lat, lon) AS cnt_lat_lon
FROM Insurance
)
WHERE cnt_tiv_2015 > 1 AND cnt_lat_lon = 1
)
SELECT
ROUND(SUM(tiv_2016)::numeric, 2) AS tiv_2016
FROM Insurance AS i
JOIN valid_pids AS vp
ON i.pid = vp.pid;
import pandas as pd
# WORKS
def find_investments(insurance: pd.DataFrame) -> pd.DataFrame:
valid_tiv_2015 = insurance.groupby("tiv_2015").size()
valid_tiv_2015 = valid_tiv_2015[valid_tiv_2015 > 1].index
valid_lat_lon = (
insurance.groupby(["lat", "lon"], as_index=False)
.size()
.loc[lambda df: df["size"] == 1]
[["lat", "lon"]]
)
valid_pids = insurance.loc[insurance["tiv_2015"].isin(valid_tiv_2015)]
valid_pids = valid_pids.merge(
valid_lat_lon,
on=["lat", "lon"],
how="inner"
)
tiv_2016 = valid_pids["tiv_2016"].sum().round(2)
return pd.DataFrame({"tiv_2016": [tiv_2016]})
# Error: Cannot index with multidimensional key
def find_investments(insurance: pd.DataFrame) -> pd.DataFrame:
valid_tiv_2015 = insurance.groupby("tiv_2015").size()
valid_tiv_2015 = valid_tiv_2015[valid_tiv_2015 > 1].index
valid_lat_lon = insurance.groupby(["lat", "lon"]).size()
valid_lat_lon = valid_lat_lon[valid_lat_lon == 1].index
valid_pids = insurance.loc[insurance[["lat", "lon"]].isin(valid_lat_lon)]
return valid_pids
Extra¶
Complement on Pandas MultiIndex and other solutions¶
In the Pandas comments, what do you mean by a tuple key? Is it possible on MultiIndex?
tuple key¶
A tuple key means turning the two columns into one comparable value:
So instead of asking:
You ask:
That matters because location uniqueness is about the pair, not each column separately.
Why your failing code fails¶
You had roughly:
valid_lat_lon = insurance.groupby(["lat", "lon"]).size()
valid_lat_lon = valid_lat_lon[valid_lat_lon == 1].index
valid_pids = insurance.loc[
insurance[["lat", "lon"]].isin(valid_lat_lon)
]
Problem:
insurance[["lat", "lon"]]is a DataFrame..isin(valid_lat_lon)does elementwise checks.- It does not compare row pairs like
(lat, lon). - It returns a 2D boolean DataFrame.
.loc[...]expects a 1D row mask, so you get:
Option 1: use tuple keys¶
location_key = pd.Series(
list(zip(insurance["lat"], insurance["lon"])),
index=insurance.index,
)
location_counts = location_key.value_counts()
valid_locations = location_counts[location_counts == 1].index
valid_rows = insurance[location_key.isin(valid_locations)]
Here location_key looks like:
So .isin(...) compares full pairs.
Option 2: use MultiIndex¶
Yes, this is possible and often cleaner.
location_counts = insurance.groupby(["lat", "lon"]).size()
valid_locations = location_counts[location_counts == 1].index
row_locations = pd.MultiIndex.from_frame(insurance[["lat", "lon"]])
valid_rows = insurance[row_locations.isin(valid_locations)]
This works because:
valid_locationsis aMultiIndex.row_locationsis also aMultiIndex..isin()compares full index keys like(lat, lon).
Full Pandas solution using MultiIndex¶
import pandas as pd
def find_investments(insurance: pd.DataFrame) -> pd.DataFrame:
tiv_2015_counts = insurance.groupby("tiv_2015").size()
valid_tiv_2015 = tiv_2015_counts[tiv_2015_counts > 1].index
location_counts = insurance.groupby(["lat", "lon"]).size()
valid_locations = location_counts[location_counts == 1].index
row_locations = pd.MultiIndex.from_frame(insurance[["lat", "lon"]])
mask = (
insurance["tiv_2015"].isin(valid_tiv_2015)
& row_locations.isin(valid_locations)
)
total = insurance.loc[mask, "tiv_2016"].sum().round(2)
return pd.DataFrame({"tiv_2016": [total]})
Best interview version¶
The cleanest Pandas answer is still usually transform, because it avoids manual joins, tuple keys, and MultiIndex handling:
import pandas as pd
def find_investments(insurance: pd.DataFrame) -> pd.DataFrame:
tiv_2015_count = insurance.groupby("tiv_2015")["pid"].transform("size")
location_count = insurance.groupby(["lat", "lon"])["pid"].transform("size")
mask = (tiv_2015_count > 1) & (location_count == 1)
total = insurance.loc[mask, "tiv_2016"].sum().round(2)
return pd.DataFrame({"tiv_2016": [total]})
Mental model¶
Use this rule:
- If you need column-by-column membership, use DataFrame
.isin(...). - If you need row-pair membership, create one key:
- Tuple key:
(lat, lon) MultiIndex: index key(lat, lon)merge: relational key["lat", "lon"]
- Tuple key:
For this problem, lat and lon must be treated as one combined key.