Mapping Colors
Red Green Mapping¶
I am going to define many functions below for mapping numerical values to red and green values. The first two are not necessary to understand. The first makes a conversion from an rgb value to a hex value and the second takes a value between -1 and 1 and converts negative values to darker versions of red depending on how negative they are and darker versions of green depending on how positive they are.
def rgb_to_hex(rgb):
return "#" + '%02x%02x%02x' % rgb
def value_to_red_green(value):
if value > 0:
return (int(255-value*255), 255, int(255-value*255))
else:
value = abs(value)
return (255, int(255-value*255), int(255-value*255))
The next function is going to define how we take a series of values and convert them to these red and green values. The first step is to normalize the values to be between -1 and 1 based on this formula:
$ Y_t = (\frac{X_t-MIN(X_t)}{MAX(X_t)-MIN(X_t)} - .5) * 2 $
After that it is converted to be a red green value.
def create_color_scale(values):
#Find the value
scale = ((values - values.min()) / (values.max() - values.min()) - .5) * 2
#Map the conversion to rgb
scale = scale.apply(value_to_red_green)
#Map rgb to hex
scale = scale.apply(rgb_to_hex)
return scale
#Get the colors
area_averages["Color"] = create_color_scale(area_averages['AV_LAND'])
print(area_averages["Color"])
Now we are able to plot all of these on the map with colors!
boston_map = folium.Map(location=[42.35, -71], zoom_start=12)
#Plot each rectangle
for i in area_averages.index:
lat1, lat2, lon1, lon2 = area_averages.loc[i][['Latitude Lower', 'Latitude Upper',
'Longitude Lower', 'Longitude Upper']]
#Get the color
fill_color = area_averages.loc[i, "Color"]
folium.vector_layers.Rectangle([[lat1, lon1],
[lat1, lon2],
[lat2, lon2],
[lat2, lon1]], color='#000000', fill=True, fill_color=fill_color,fill_opacity=.8).add_to(boston_map)
boston_map
A modification that might make more sense is to do percentile ranking for the properties to color it so that the median value has no color, and then higher percentiles are more green, lower percentiles are more red.
def create_color_scale_percentile(values):
#Find the value
scale = (values.rank(pct=True) - .5) * 2
#Map the conversion to rgb
scale = scale.apply(value_to_red_green)
#Map rgb to hex
scale = scale.apply(rgb_to_hex)
return scale
#Get the colors
area_averages["Color"] = create_color_scale_percentile(area_averages['AV_LAND'])
print(area_averages["Color"])
boston_map = folium.Map(location=[42.35, -71], zoom_start=12)
#Plot each rectangle
for i in area_averages.index:
lat1, lat2, lon1, lon2 = area_averages.loc[i][['Latitude Lower', 'Latitude Upper',
'Longitude Lower', 'Longitude Upper']]
#Get the color
fill_color = area_averages.loc[i, "Color"]
folium.vector_layers.Rectangle([[lat1, lon1],
[lat1, lon2],
[lat2, lon2],
[lat2, lon1]], color='#000000', fill=True, fill_color=fill_color,fill_opacity=.8).add_to(boston_map)
boston_map
Let's create three new fields and also build a function to easily visualize any given field. The three new fields will be:
- The land value divided by the surface area of the land.
- The total value divided by the surface area of the land.
- The building value divided by the land value.
df["AV_LAND/SF"] = df["AV_LAND"].where(df['AV_LAND'] != 0) / df['LAND_SF'].where(df['LAND_SF'] != 0)
df["AV_TOTAL/SF"] = df["AV_TOTAL"].where(df['AV_TOTAL'] != 0) / df['LAND_SF'].where(df['LAND_SF'] != 0)
df['AV_BLDG/AV_LAND'] = df['AV_BLDG'].where(df['AV_BLDG'] != 0) / df["AV_LAND"].where(df['AV_LAND'] != 0)
def create_map(LU, key):
#Filter to sample where the LU matches
sample = df[df['LU'] == LU].copy()
#Get rid of any null values
sample = sample[~pd.isnull(sample['Location'])]
#Get the latitude
sample['Latitude'] = sample['Location'].apply(lambda x: x.replace("(", "").replace(")", "").split("| ")[0]).astype(float)
#Get the longitude
sample['Longitude'] = sample['Location'].apply(lambda x: x.replace("(", "").replace(")", "").split("| ")[1]).astype(float)
#Get rid of any that have 0 for the latitude (not a valid location)
sample = sample[sample['Latitude'] != 0]
#Find the min/max values
lat_min = sample['Latitude'].min()
lat_max = sample['Latitude'].max()
lon_min = sample['Longitude'].min()
lon_max = sample['Longitude'].max()
#Create the groups
sample['Latitude Group'], lat_bins = pd.cut(sample['Latitude'], 100, labels=list(range(100)), retbins=True)
sample['Longitude Group'], lon_bins = pd.cut(sample['Longitude'], 100, labels=list(range(100)), retbins=True)
#Get rid of null values
sample = sample[~pd.isnull(sample[key])]
#Create the area averages
area_averages = sample.groupby(['Latitude Group', 'Longitude Group'])[key].mean().dropna()
#Reset the index
area_averages = area_averages.reset_index()
area_averages.columns = ['Latitude Group', 'Longitude Group', key]
#Get the colors
area_averages["Color"] = create_color_scale_percentile(area_averages[key])
#Get the bounds
area_averages['Latitude Lower'] = area_averages['Latitude Group'].apply(lambda x: lat_bins[x])
area_averages['Latitude Upper'] = area_averages['Latitude Group'].apply(lambda x: lat_bins[x+1])
area_averages['Longitude Lower'] = area_averages['Longitude Group'].apply(lambda x: lon_bins[x])
area_averages['Longitude Upper'] = area_averages['Longitude Group'].apply(lambda x: lon_bins[x+1])
#Build the map
boston_map = folium.Map(location=[42.35, -71], zoom_start=12)
#Plot each rectangle
for i in area_averages.index:
lat1, lat2, lon1, lon2 = area_averages.loc[i][['Latitude Lower', 'Latitude Upper',
'Longitude Lower', 'Longitude Upper']]
#Get the color
fill_color = area_averages.loc[i, "Color"]
folium.vector_layers.Rectangle([[lat1, lon1],
[lat1, lon2],
[lat2, lon2],
[lat2, lon1]], color='#000000', fill=True, fill_color=fill_color,fill_opacity=.8).add_to(boston_map)
return boston_map
create_map("R1", "AV_LAND/SF")
create_map("A", "AV_TOTAL")
#Add in the hover tools