pvd-property-taxes-2019

Created by SmirkyGraphs. Code: Github. Source: OpenPVD.


Providence Property Taxes 2019 Analysis

Providence posts a spreadsheet of their full property tax roll every year on OpenPVD. This notebook will be used to view the changes between 2018 and 2019 by 3 geographic regions using Zip codes, Wards, and Neighborhoods. The goal is to see how much taxes have increased or decreased for each region and how much they pay in taxes compared to one another.

This data has 356 parcels removed, due to them not being in the parcel shapefile
The ward, zip code and neighborhoods are based on the centroids of the parcel

In [1]:
import pandas as pd
import numpy as np
In [2]:
# load data (this dataset has 356 properties removed due to no location info)
df = pd.read_csv('./data/reports/2019_pvd_property_tax_clean.csv')
In [3]:
# get total taxes for 2018 -> $280,423,906.64
total_2018 = df.query('year == 2018 & taxes > 0')['taxes'].sum()

# get total taxes for 2019 -> $284,756,820.28
total_2019 = df.query('year == 2019 & taxes > 0')['taxes'].sum()

# get percent increase
change = round(((total_2019 - total_2018)/total_2018)*100, 2)

print(f'total taxes 2018: {total_2018}')
print(f'total taxes 2019: {total_2019}')
print(f'          change: {change}%')
total taxes 2018: 280423906.64
total taxes 2019: 284756820.28
          change: 1.55%
In [4]:
increase = df[(df['tax_chng'] > 0) & (df['year']==2019)].shape[0]
decrease = df[(df['tax_chng'] < 0) & (df['year']==2019)].shape[0]
same = df[(df['tax_chng'] == 0) & (df['year']==2019)].shape[0]
exempt = df[(df['assessment'] == df['exemption']) & (df['year']==2019)].shape[0]

print(f'increased: {increase}')
print(f'decreased: {decrease}')
print(f'     same: {same}')
print(f'   exempt: {exempt}')
increased: 24874
decreased: 15107
     same: 1058
   exempt: 2515
In [5]:
# remove exempt properties from the dataset 
df = df.query('taxes > 0')
In [6]:
# 2-5 Family and Single Family Homes make up ~45% of taxes and will be the focus of this
table = df.pivot_table(index='class', values='taxes', aggfunc='sum', columns='year')
table = table/table.sum()

table = table.sort_values(by=2019, ascending=False)

table[2018] = table[2018].map(lambda n: '{:,.1%}'.format(n))
table[2019] = table[2019].map(lambda n: '{:,.1%}'.format(n))

table
Out[6]:
year 2018 2019
class
2 -5 Family 21.9% 23.4%
Single Family 23.8% 22.9%
Commercial II 20.0% 19.1%
Apartment Building 5.6% 6.1%
Residential Condo 6.1% 5.5%
Industrial 4.7% 4.7%
44-3-9 Stb 3.5% 4.1%
Commercial I 4.0% 4.0%
Combination 3.6% 3.7%
CI Vacant Land 2.5% 2.3%
Miscellaneous 2.1% 2.1%
Commercial Condo 1.4% 1.4%
Residential Vacant Land 0.7% 0.6%
Utility 0.1% 0.1%

From here down only single family and 2-5 family homes are used.

In [7]:
# filter for only single family and 2-5 family homes
df = df[(df['class'] == 'Single Family') | (df['class'] == '2 -5 Family')]

Assessments

Median Assessment by Zip Code

In [8]:
# this table will show the median assessment for each zip code
table = df.pivot_table(index='zip_codes', values='assessment', aggfunc='median', columns='year')
table.sort_values(by=2019, ascending=False)
Out[8]:
year 2018 2019
zip_codes
2906 388200.0 462250.0
2903 185550.0 299100.0
2908 142400.0 202300.0
2909 132900.0 189700.0
2904 132100.0 188800.0
2907 131700.0 188300.0
2905 126200.0 174750.0

Median Assessment Change from 2018 to 2019 by Zip Code

In [9]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='zip_codes', values=['assess_chng', 'assess_%_chng'], aggfunc='median')
table = table.sort_values(by=('assess_%_chng'), ascending=False)

table['assess_%_chng'] = table['assess_%_chng'].map(lambda n: '{:,.1%}'.format(n))

table
Out[9]:
assess_%_chng assess_chng
zip_codes
2903 56.7% 103800.0
2907 42.2% 54600.0
2909 41.9% 56100.0
2904 40.9% 53100.0
2908 38.8% 54300.0
2905 38.4% 46700.0
2906 19.6% 72500.0

Median Assessment per Square Feet by Zip Code

In [10]:
# this table shows the median assessment per square foot
table = df.pivot_table(index='zip_codes', values='assess_pr_sqrft', aggfunc='median', columns='year')

table[2018] = round(table[2018], 2)
table[2019] = round(table[2019], 2)

table.sort_values(by=2019, ascending=False)
Out[10]:
year 2018 2019
zip_codes
2903 60.08 94.04
2906 76.02 89.69
2905 34.85 48.30
2908 32.78 46.24
2909 30.95 44.42
2907 29.64 42.62
2904 28.34 40.04

Median Assessment per Square Feet Change from 2018 to 2019 by Zip Code

In [11]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased (for square footage)
table = df.pivot_table(index='zip_codes', values=['assess_pr_sqrft_%_chng', 'assess_pr_sqrft_chng'], aggfunc='median')

table['assess_pr_sqrft_%_chng'] = table['assess_pr_sqrft_%_chng'].map(lambda n: '{:,.1%}'.format(n))
table['assess_pr_sqrft_chng'] = round(table['assess_pr_sqrft_chng'], 2)

table.sort_values(by=('assess_pr_sqrft_%_chng'), ascending=False)
Out[11]:
assess_pr_sqrft_%_chng assess_pr_sqrft_chng
zip_codes
2903 56.8% 32.15
2907 42.2% 12.55
2909 41.9% 12.96
2904 40.9% 11.25
2908 38.8% 12.38
2905 38.4% 12.64
2906 19.6% 13.92

Taxes

Whose Taxes Increase/Decreased in 2019 by Zip Code

In [12]:
# getting table of % increase/decrease for 2019
table = df.query('year > 2018').copy()

# add a basic count
table['count'] = 1

# get increase, decrease or same
table.loc[table['tax_chng'] > 0, 'change'] = 'increase'
table.loc[table['tax_chng'] == 0, 'change'] = 'same'
table.loc[table['tax_chng'] < 0, 'change'] = 'decrease'

table = table.pivot_table(index='zip_codes', values='count', aggfunc='count', columns='change')
table = table.div(table.sum(axis=1), axis=0).sort_values(by='increase', ascending=False)

table['decrease'] = table['decrease'].map(lambda n: '{:,.1%}'.format(n))
table['increase'] = table['increase'].map(lambda n: '{:,.1%}'.format(n))

table.sort_values(by='increase', ascending=False)
Out[12]:
change decrease increase
zip_codes
2904 15.2% 84.8%
2903 16.5% 83.5%
2909 17.7% 82.3%
2907 20.9% 79.1%
2908 21.8% 78.2%
2905 26.9% 73.1%
2906 71.8% 28.2%

Total Property Tax Burden by Zip Code

In [13]:
# this table will show zip codes as a percent of total taxes payed
table = df.pivot_table(index='zip_codes', values='assessment', aggfunc='sum', columns='year')

# getting table as percentages
table = table/table.sum()

table = table.sort_values(by=2019, ascending=False)
table[2018] = table[2018].map(lambda n: '{:,.1%}'.format(n))
table[2019] = table[2019].map(lambda n: '{:,.1%}'.format(n))

table
Out[13]:
year 2018 2019
zip_codes
2906 44.1% 39.7%
2908 18.9% 20.1%
2909 14.5% 15.8%
2907 9.2% 10.0%
2905 5.5% 5.9%
2904 5.3% 5.7%
2903 2.5% 2.9%

Median Taxes By Zip Code

In [14]:
# this table will show the median taxes due for each zip code
table = df.pivot_table(index='zip_codes', values='taxes', aggfunc='median', columns='year')
table.sort_values(by=2019, ascending=False)
Out[14]:
year 2018 2019
zip_codes
2906 8319.00 7652.30
2903 5058.06 6031.96
2908 3028.68 3319.48
2909 2914.76 3272.28
2907 2788.04 3157.32
2904 2698.56 2977.48
2905 2726.00 2932.88

Median Taxes Change from 2018 to 2019 by Zip Code

In [15]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='zip_codes', values=['tax_chng', 'tax_%_chng'], aggfunc='median')
table = table.sort_values(by=('tax_%_chng'), ascending=False)

table['tax_%_chng'] = table['tax_%_chng'].map(lambda n: '{:,.1%}'.format(n))

table
Out[15]:
tax_%_chng tax_chng
zip_codes
2903 22.1% 905.32
2909 10.7% 300.14
2907 10.6% 288.28
2904 10.0% 265.12
2908 8.4% 237.44
2905 7.5% 195.80
2906 -6.8% -512.04

Median Tax per Square Feet by Zip Code

In [16]:
# this table will show median tax per sqrft
table = df.pivot_table(index='zip_codes', values='tax_pr_sqrft', aggfunc='median', columns='year')

table[2018] = round(table[2018], 2)
table[2019] = round(table[2019], 2)

table.sort_values(by=2019, ascending=False)
Out[16]:
year 2018 2019
zip_codes
2903 1.56 1.91
2906 1.55 1.42
2905 0.74 0.80
2908 0.69 0.75
2909 0.67 0.75
2907 0.64 0.71
2904 0.58 0.64

Median Tax per Square Feet Change from 2018 to 2019 by Zip Code

In [17]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='zip_codes', values=['taxes_pr_sqrft_%_chng', 'taxes_pr_sqrft_chng'], aggfunc='median')
table = table.sort_values(by=('taxes_pr_sqrft_%_chng'), ascending=False)

table['taxes_pr_sqrft_%_chng'] = table['taxes_pr_sqrft_%_chng'].map(lambda n: '{:,.1%}'.format(n))
table['taxes_pr_sqrft_chng'] = round(table['taxes_pr_sqrft_chng'], 2)

table
Out[17]:
taxes_pr_sqrft_%_chng taxes_pr_sqrft_chng
zip_codes
2903 22.1% 0.28
2909 10.7% 0.07
2907 10.6% 0.07
2904 10.0% 0.06
2908 8.4% 0.05
2905 7.5% 0.05
2906 -6.8% -0.10

Assessments

Median Assessment by Ward

In [18]:
# this table will show the median assessment for each zip code
table = df.pivot_table(index='ward', values='assessment', aggfunc='median', columns='year')
table.sort_values(by=2019, ascending=False)
Out[18]:
year 2018 2019
ward
2 534300.0 621350.0
1 351200.0 443150.0
3 316400.0 382300.0
13 180600.0 283400.0
5 153700.0 212900.0
12 137700.0 203550.0
9 127300.0 192800.0
14 140300.0 191600.0
6 123100.0 188400.0
15 132500.0 188400.0
11 130750.0 186450.0
4 129350.0 184500.0
8 131550.0 179700.0
7 128500.0 177700.0
10 129000.0 176600.0

Median Assessment Change from 2018 to 2019 by Ward

In [19]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='ward', values=['assess_chng', 'assess_%_chng'], aggfunc='median')
table = table.sort_values(by=('assess_%_chng'), ascending=False)

table['assess_%_chng'] = table['assess_%_chng'].map(lambda n: '{:,.1%}'.format(n))

table
Out[19]:
assess_%_chng assess_chng
ward
13 54.1% 96600.0
6 52.3% 62800.0
9 49.5% 63200.0
12 43.5% 60150.0
11 42.7% 55150.0
15 41.6% 55700.0
4 40.1% 51900.0
7 37.9% 47900.0
5 37.5% 55400.0
10 37.0% 45900.0
14 36.2% 48400.0
8 35.9% 46100.0
1 25.7% 88350.0
3 22.2% 63500.0
2 14.5% 78800.0

Median Assessment per Square Feet by Ward

In [20]:
# this table shows the median assessment per square foot
table = df.pivot_table(index='ward', values='assess_pr_sqrft', aggfunc='median', columns='year')

table[2018] = round(table[2018], 2)
table[2019] = round(table[2019], 2)

table.sort_values(by=2019, ascending=False)
Out[20]:
year 2018 2019
ward
1 89.64 113.56
2 90.13 104.43
3 65.19 78.45
13 48.36 74.92
12 36.51 53.12
10 35.60 48.66
11 33.23 48.19
6 30.57 46.59
15 32.08 45.95
9 30.14 44.81
5 31.81 44.04
14 30.82 42.10
8 29.24 40.42
4 28.76 40.24
7 28.16 39.03

Median Assessment per Square Feet Change from 2018 to 2019 by Ward

In [21]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased (for square footage)
table = df.pivot_table(index='ward', values=['assess_pr_sqrft_%_chng', 'assess_pr_sqrft_chng'], aggfunc='median')

table['assess_pr_sqrft_%_chng'] = table['assess_pr_sqrft_%_chng'].map(lambda n: '{:,.1%}'.format(n))
table['assess_pr_sqrft_chng'] = round(table['assess_pr_sqrft_chng'], 2)

table.sort_values(by=('assess_pr_sqrft_%_chng'), ascending=False)
Out[21]:
assess_pr_sqrft_%_chng assess_pr_sqrft_chng
ward
13 54.1% 25.67
6 52.3% 15.40
9 49.5% 14.43
12 43.5% 15.43
11 42.7% 14.07
15 41.6% 13.32
4 40.1% 11.04
7 37.9% 10.54
5 37.5% 11.41
10 37.0% 12.47
14 36.2% 10.77
8 35.9% 10.38
1 25.7% 22.23
3 22.2% 12.75
2 14.5% 12.65

Taxes

Whose Taxes Increase/Decreased in 2019 by Ward

In [22]:
# getting table of % increase/decrease for 2019
table = df.query('year > 2018').copy()

# add a basic count
table['count'] = 1

# get increase, decrease or same
table.loc[table['tax_chng'] > 0, 'change'] = 'increase'
table.loc[table['tax_chng'] == 0, 'change'] = 'same'
table.loc[table['tax_chng'] < 0, 'change'] = 'decrease'

table = table.pivot_table(index='ward', values='count', aggfunc='count', columns='change')
table = table.div(table.sum(axis=1), axis=0).sort_values(by='increase', ascending=False)

table['decrease'] = table['decrease'].map(lambda n: '{:,.1%}'.format(n))
table['increase'] = table['increase'].map(lambda n: '{:,.1%}'.format(n))

table.sort_values(by='increase', ascending=False)
Out[22]:
change decrease increase
ward
6 7.1% 92.9%
9 11.7% 88.3%
13 14.6% 85.4%
15 17.7% 82.3%
4 19.0% 81.0%
7 20.2% 79.8%
11 21.1% 78.9%
12 21.2% 78.8%
14 22.0% 78.0%
5 24.7% 75.3%
8 29.6% 70.4%
10 30.2% 69.8%
1 60.7% 39.3%
3 66.4% 33.6%
2 82.2% 17.8%

Total Property Tax Burden by Ward

In [23]:
# this table will show zip codes as a percent of total taxes payed
table = df.pivot_table(index='ward', values='assessment', aggfunc='sum', columns='year')

# getting table as percentages
table = table/table.sum()

table = table.sort_values(by=2019, ascending=False)
table[2018] = table[2018].map(lambda n: '{:,.1%}'.format(n))
table[2019] = table[2019].map(lambda n: '{:,.1%}'.format(n))

table
Out[23]:
year 2018 2019
ward
2 21.1% 18.5%
3 16.1% 14.6%
5 8.2% 8.7%
1 7.6% 7.3%
14 5.5% 5.8%
4 5.1% 5.5%
13 4.5% 5.2%
6 4.4% 5.1%
7 4.5% 4.8%
12 4.3% 4.6%
8 4.3% 4.5%
9 3.9% 4.5%
10 4.2% 4.4%
15 3.4% 3.7%
11 2.7% 2.9%

Median Taxes By Ward

In [24]:
# this table will show the median taxes due for each zip code
table = df.pivot_table(index='ward', values='taxes', aggfunc='median', columns='year')
table.sort_values(by=2019, ascending=False)
Out[24]:
year 2018 2019
ward
2 10796.84 9667.24
1 8670.76 8610.76
3 6328.08 5919.92
13 4406.72 5139.84
12 3458.36 3817.16
11 3149.20 3379.18
15 2972.28 3325.40
5 3050.18 3296.66
9 2680.08 3186.12
6 2668.68 3181.22
14 2893.32 3065.12
10 2786.16 2986.52
4 2695.74 2964.24
8 2744.80 2944.34
7 2672.42 2889.78

Median Taxes Change from 2018 to 2019 by Ward

In [25]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='ward', values=['tax_chng', 'tax_%_chng'], aggfunc='median')
table = table.sort_values(by=('tax_%_chng'), ascending=False)

table['tax_%_chng'] = table['tax_%_chng'].map(lambda n: '{:,.1%}'.format(n))

table
Out[25]:
tax_%_chng tax_chng
ward
13 19.8% 789.56
6 18.8% 477.28
9 16.7% 438.68
12 11.2% 343.08
11 10.8% 312.86
15 10.4% 295.26
4 9.3% 246.40
5 7.5% 209.42
7 7.3% 198.48
14 6.3% 170.08
10 6.1% 165.70
8 5.8% 153.24
1 -2.3% -170.90
3 -5.0% -319.48
2 -10.7% -1056.48

Median Tax per Square Feet by Ward

In [26]:
# this table will show median tax per sqrft
table = df.pivot_table(index='ward', values='tax_pr_sqrft', aggfunc='median', columns='year')

table[2018] = round(table[2018], 2)
table[2019] = round(table[2019], 2)

table.sort_values(by=2019, ascending=False)
Out[26]:
year 2018 2019
ward
1 2.14 2.09
2 1.75 1.57
13 1.14 1.33
3 1.31 1.21
12 0.92 1.03
11 0.78 0.85
10 0.77 0.81
6 0.66 0.79
15 0.71 0.78
9 0.62 0.72
5 0.63 0.67
8 0.62 0.67
14 0.63 0.67
4 0.59 0.65
7 0.59 0.64

Median Tax per Square Feet Change from 2018 to 2019 by Ward

In [27]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='ward', values=['taxes_pr_sqrft_%_chng', 'taxes_pr_sqrft_chng'], aggfunc='median')
table = table.sort_values(by=('taxes_pr_sqrft_%_chng'), ascending=False)

table['taxes_pr_sqrft_%_chng'] = table['taxes_pr_sqrft_%_chng'].map(lambda n: '{:,.1%}'.format(n))
table['taxes_pr_sqrft_chng'] = round(table['taxes_pr_sqrft_chng'], 2)

table
Out[27]:
taxes_pr_sqrft_%_chng taxes_pr_sqrft_chng
ward
13 19.8% 0.21
6 18.8% 0.11
9 16.7% 0.10
12 11.3% 0.09
11 10.8% 0.08
15 10.4% 0.07
4 9.3% 0.05
5 7.5% 0.04
7 7.3% 0.04
14 6.3% 0.03
10 6.1% 0.05
8 5.8% 0.03
1 -2.3% -0.04
3 -5.0% -0.06
2 -10.7% -0.16

Assessments

Median Assessment by Neighborhood

In [28]:
# this table will show the median assessment for each zip code
table = df.pivot_table(index='neighborhood', values='assessment', aggfunc='median', columns='year')
table.sort_values(by=2019, ascending=False)
Out[28]:
year 2018 2019
neighborhood
College Hill 590850.0 722400.0
Blackstone 469050.0 542900.0
Wayland 454200.0 533000.0
Downtown 439950.0 448350.0
Fox Point 322000.0 407900.0
Hope 288400.0 357000.0
Mount Hope 266100.0 334900.0
Federal Hill 178400.0 295900.0
Elmhurst 160850.0 222700.0
Elmwood 133600.0 205000.0
Valley 126800.0 201500.0
West End 135300.0 196600.0
Mount Pleasant 134700.0 195150.0
Smith Hill 127650.0 190000.0
South Elmwood 139700.0 190000.0
Charles 132500.0 189500.0
Silver Lake 132400.0 185100.0
Manton 127400.0 183400.0
Reservoir 137150.0 180600.0
Olneyville 119400.0 179650.0
Hartford 130500.0 178300.0
Washington Park 130600.0 176600.0
Upper South Providence 124000.0 175700.0
Lower South Providence 120150.0 173100.0
Wanskuck 121100.0 166000.0

Median Assessment Change from 2018 to 2019 by Neighborhood

In [29]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='neighborhood', values=['assess_chng', 'assess_%_chng'], aggfunc='median')
table = table.sort_values(by=('assess_%_chng'), ascending=False)

table['assess_%_chng'] = table['assess_%_chng'].map(lambda n: '{:,.1%}'.format(n))

table
Out[29]:
assess_%_chng assess_chng
neighborhood
Federal Hill 62.0% 111300.0
Valley 54.2% 69300.0
Elmwood 52.0% 69400.0
Olneyville 50.4% 59150.0
Manton 46.8% 57300.0
Smith Hill 45.2% 58300.0
Mount Pleasant 43.6% 56700.0
Upper South Providence 43.2% 52400.0
Charles 41.9% 53900.0
Lower South Providence 41.0% 50300.0
Silver Lake 39.6% 51250.0
West End 38.5% 56000.0
Wanskuck 36.4% 43600.0
Elmhurst 36.2% 56100.0
Hartford 36.1% 46150.0
South Elmwood 34.9% 47700.0
Washington Park 34.6% 44500.0
Reservoir 32.9% 44100.0
Mount Hope 29.2% 66500.0
Fox Point 26.5% 85000.0
Hope 24.3% 63450.0
College Hill 21.9% 121600.0
Wayland 17.3% 81300.0
Blackstone 14.1% 66400.0
Downtown 4.3% 25100.0

Median Assessment per Square Feet by Neighborhood

In [30]:
# this table shows the median assessment per square foot
table = df.pivot_table(index='neighborhood', values='assess_pr_sqrft', aggfunc='median', columns='year')

table[2018] = round(table[2018], 2)
table[2019] = round(table[2019], 2)

table.sort_values(by=2019, ascending=False)
Out[30]:
year 2018 2019
neighborhood
College Hill 108.85 130.48
Fox Point 93.88 118.89
Wayland 92.72 111.31
Downtown 102.74 107.31
Blackstone 79.23 90.54
Federal Hill 51.64 84.72
Hope 60.42 75.24
Mount Hope 55.41 71.38
Smith Hill 35.33 52.15
Valley 33.41 51.85
Olneyville 33.42 49.73
Washington Park 35.66 48.84
West End 33.87 47.98
Lower South Providence 32.71 47.22
Elmhurst 33.78 46.68
Mount Pleasant 31.57 45.47
Silver Lake 31.23 44.23
Elmwood 28.53 43.58
Charles 29.75 42.16
Upper South Providence 29.87 41.93
South Elmwood 30.06 41.42
Reservoir 29.17 39.02
Wanskuck 28.05 38.40
Manton 25.36 36.79
Hartford 26.62 36.54

Median Assessment per Square Feet Change from 2018 to 2019 by Neighborhood

In [31]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased (for square footage)
table = df.pivot_table(index='neighborhood', values=['assess_pr_sqrft_%_chng', 'assess_pr_sqrft_chng'], aggfunc='median')

table['assess_pr_sqrft_%_chng'] = table['assess_pr_sqrft_%_chng'].map(lambda n: '{:,.1%}'.format(n))
table['assess_pr_sqrft_chng'] = round(table['assess_pr_sqrft_chng'], 2)

table.sort_values(by=('assess_pr_sqrft_%_chng'), ascending=False)
Out[31]:
assess_pr_sqrft_%_chng assess_pr_sqrft_chng
neighborhood
Federal Hill 62.0% 31.95
Valley 54.2% 17.87
Elmwood 52.0% 14.85
Olneyville 50.4% 16.11
Manton 46.8% 10.87
Smith Hill 45.2% 15.85
Mount Pleasant 43.6% 13.08
Upper South Providence 43.2% 12.33
Charles 41.9% 11.91
Lower South Providence 41.0% 13.90
Downtown 4.3% 4.56
Silver Lake 39.6% 12.51
West End 38.5% 13.44
Wanskuck 36.4% 9.82
Elmhurst 36.2% 11.72
Hartford 36.1% 9.42
South Elmwood 34.9% 10.35
Washington Park 34.6% 12.09
Reservoir 32.9% 9.41
Mount Hope 29.2% 14.45
Fox Point 26.5% 23.68
Hope 24.3% 13.35
College Hill 21.9% 21.89
Wayland 17.3% 15.44
Blackstone 14.1% 10.75

Taxes

Whose Taxes Increase/Decreased in 2019 by Neighborhood

In [32]:
# getting table of % increase/decrease for 2019
table = df.query('year > 2018').copy()

# add a basic count
table['count'] = 1

# get increase, decrease or same
table.loc[table['tax_chng'] > 0, 'change'] = 'increase'
table.loc[table['tax_chng'] == 0, 'change'] = 'same'
table.loc[table['tax_chng'] < 0, 'change'] = 'decrease'

table = table.pivot_table(index='neighborhood', values='count', aggfunc='count', columns='change')
table = table.div(table.sum(axis=1), axis=0).sort_values(by='increase', ascending=False)

table['decrease'] = table['decrease'].map(lambda n: '{:,.1%}'.format(n))
table['increase'] = table['increase'].map(lambda n: '{:,.1%}'.format(n))

table.sort_values(by='increase', ascending=False)
Out[32]:
change decrease increase
neighborhood
Federal Hill 7.4% 92.6%
Valley 9.4% 90.6%
Elmwood 10.1% 89.9%
Charles 14.4% 85.6%
Manton 15.4% 84.6%
Olneyville 15.6% 84.4%
Smith Hill 16.6% 83.4%
Silver Lake 16.6% 83.4%
Mount Pleasant 17.8% 82.2%
Lower South Providence 19.6% 80.4%
Upper South Providence 19.6% 80.4%
Elmhurst 23.3% 76.7%
Hartford 23.5% 76.5%
West End 25.1% 74.9%
South Elmwood 25.7% 74.3%
Wanskuck 25.8% 74.2%
Washington Park 31.4% 68.6%
Reservoir 37.5% 62.5%
Mount Hope 47.3% 52.7%
Fox Point 58.2% 41.8%
Hope 65.5% 34.5%
College Hill 66.1% 33.9%
Downtown 75.0% 25.0%
Wayland 75.7% 24.3%
Blackstone 86.9% 13.1%

Total Property Tax Burden by Neighborhood

In [33]:
# this table will show zip codes as a percent of total taxes payed
table = df.pivot_table(index='neighborhood', values='assessment', aggfunc='sum', columns='year')

# getting table as percentages
table = table/table.sum()

table = table.sort_values(by=2019, ascending=False)
table[2018] = table[2018].map(lambda n: '{:,.1%}'.format(n))
table[2019] = table[2019].map(lambda n: '{:,.1%}'.format(n))

table
Out[33]:
year 2018 2019
neighborhood
Blackstone 19.1% 16.5%
Elmhurst 8.2% 8.4%
College Hill 8.0% 7.4%
Mount Pleasant 6.0% 6.6%
West End 4.7% 5.0%
Silver Lake 4.5% 4.8%
Wayland 5.4% 4.8%
Hope 4.7% 4.4%
Wanskuck 4.0% 4.2%
Fox Point 4.3% 4.1%
Charles 3.6% 3.9%
Washington Park 3.5% 3.7%
Elmwood 3.1% 3.7%
Mount Hope 3.9% 3.7%
Federal Hill 2.7% 3.3%
Hartford 2.6% 2.7%
Smith Hill 1.9% 2.1%
Lower South Providence 1.9% 2.0%
Valley 1.5% 1.7%
Reservoir 1.7% 1.7%
Manton 1.5% 1.7%
Olneyville 1.4% 1.6%
Upper South Providence 1.0% 1.0%
South Elmwood 0.9% 1.0%
Downtown 0.0% 0.0%

Median Taxes By Neighborhood

In [34]:
# this table will show the median taxes due for each zip code
table = df.pivot_table(index='neighborhood', values='taxes', aggfunc='median', columns='year')
table.sort_values(by=2019, ascending=False)
Out[34]:
year 2018 2019
neighborhood
College Hill 12825.28 12191.80
Downtown 12662.56 10073.46
Wayland 9477.26 8769.44
Blackstone 9369.92 8496.16
Fox Point 7983.64 7748.84
Federal Hill 4791.18 5862.48
Mount Hope 5878.10 5710.20
Hope 5589.24 5307.88
Smith Hill 3223.18 3575.94
Valley 2903.96 3459.48
Elmhurst 3176.26 3458.00
West End 3236.52 3453.60
Elmwood 2801.20 3382.84
Olneyville 2924.36 3319.30
Upper South Providence 2925.28 3165.26
Silver Lake 2868.88 3150.70
Mount Pleasant 2771.12 3105.72
Manton 2701.56 3014.36
Charles 2680.88 2978.96
Lower South Providence 2710.96 2974.56
South Elmwood 2731.64 2927.68
Wanskuck 2735.40 2899.22
Washington Park 2733.06 2898.72
Reservoir 2740.10 2818.32
Hartford 2603.80 2813.88

Median Taxes Change from 2018 to 2019 by Neighborhood

In [35]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='neighborhood', values=['tax_chng', 'tax_%_chng'], aggfunc='median')
table = table.sort_values(by=('tax_%_chng'), ascending=False)

table['tax_%_chng'] = table['tax_%_chng'].map(lambda n: '{:,.1%}'.format(n))

table
Out[35]:
tax_%_chng tax_chng
neighborhood
Federal Hill 26.3% 1132.02
Valley 19.1% 551.92
Elmwood 18.6% 513.92
Olneyville 17.6% 443.12
Manton 14.0% 369.68
Smith Hill 12.8% 385.36
Mount Pleasant 12.3% 323.16
Upper South Providence 11.3% 312.86
Charles 11.1% 286.58
Lower South Providence 9.7% 260.76
Silver Lake 8.6% 240.22
West End 7.8% 227.90
Elmhurst 6.3% 201.28
Wanskuck 6.2% 155.80
Hartford 6.2% 154.28
South Elmwood 5.8% 148.34
Washington Park 5.1% 130.74
Reservoir 3.3% 94.56
Mount Hope 0.9% 49.68
Fox Point -1.7% -118.68
Hope -3.2% -170.84
College Hill -4.7% -543.60
Wayland -8.8% -860.10
Blackstone -10.8% -998.54
Downtown -19.0% -2589.10

Median Tax per Square Feet by Neighborhood

In [36]:
# this table will show median tax per sqrft
table = df.pivot_table(index='neighborhood', values='tax_pr_sqrft', aggfunc='median', columns='year')

table[2018] = round(table[2018], 2)
table[2019] = round(table[2019], 2)

table.sort_values(by=2019, ascending=False)
Out[36]:
year 2018 2019
neighborhood
Fox Point 2.19 2.16
College Hill 2.24 2.14
Downtown 2.55 2.01
Wayland 1.93 1.75
Federal Hill 1.38 1.71
Blackstone 1.53 1.36
Mount Hope 1.20 1.14
Hope 1.18 1.13
Smith Hill 0.91 1.02
Olneyville 0.78 0.92
Valley 0.76 0.91
West End 0.78 0.84
Lower South Providence 0.76 0.83
Washington Park 0.73 0.79
Upper South Providence 0.69 0.75
Silver Lake 0.67 0.73
Mount Pleasant 0.64 0.72
Elmwood 0.61 0.72
Elmhurst 0.66 0.71
Charles 0.59 0.66
Wanskuck 0.61 0.65
South Elmwood 0.59 0.63
Reservoir 0.59 0.61
Manton 0.53 0.60
Hartford 0.53 0.57

Median Tax per Square Feet Change from 2018 to 2019 by Neighborhood

In [37]:
# this table shows the median difference between 2018 & 2019 and the median percentage increased
table = df.pivot_table(index='neighborhood', values=['taxes_pr_sqrft_%_chng', 'taxes_pr_sqrft_chng'], aggfunc='median')
table = table.sort_values(by=('taxes_pr_sqrft_%_chng'), ascending=False)

table['taxes_pr_sqrft_%_chng'] = table['taxes_pr_sqrft_%_chng'].map(lambda n: '{:,.1%}'.format(n))
table['taxes_pr_sqrft_chng'] = round(table['taxes_pr_sqrft_chng'], 2)

table
Out[37]:
taxes_pr_sqrft_%_chng taxes_pr_sqrft_chng
neighborhood
Federal Hill 26.3% 0.32
Valley 19.1% 0.14
Elmwood 18.6% 0.11
Olneyville 17.6% 0.12
Manton 14.0% 0.07
Smith Hill 12.8% 0.11
Mount Pleasant 12.3% 0.07
Upper South Providence 11.3% 0.07
Charles 11.1% 0.06
Lower South Providence 9.7% 0.07
Silver Lake 8.6% 0.06
West End 7.8% 0.06
Elmhurst 6.3% 0.04
Wanskuck 6.2% 0.03
Hartford 6.2% 0.03
South Elmwood 5.8% 0.03
Washington Park 5.1% 0.03
Reservoir 3.3% 0.02
Mount Hope 0.9% 0.01
Fox Point -1.7% -0.03
Hope -3.2% -0.04
College Hill -4.7% -0.10
Wayland -8.8% -0.15
Blackstone -10.8% -0.16
Downtown -19.0% -0.47