# Data analysis
import geopandas as gpd
import numpy as np
import pandas as pd
from shapely.geometry import Point
# Plotting
import folium
import matplotlib.pyplot as plt
import matplotlib.colors
import seaborn as sns
from matplotlib import pyplot as plt
import matplotlib.colors
from matplotlib.patches import Patch
import altair as alt
import holoviews as hv
import hvplot.pandas
import geoviews as gv
import geoviews.tile_sources as gvts
import datashader as ds
import datashader.transfer_functions as tf
from datashader.colors import Greys9, viridis, inferno
import colorcet as cc
= 999
pd.options.display.max_columns import warnings
"ignore")
warnings.filterwarnings(#warnings.filterwarnings("default")
import logging
'numba.core.byteflow').setLevel(logging.WARNING)
logging.getLogger("fsspec").setLevel(logging.WARNING) logging.getLogger(
Part 2 - A Close Look Into America’s Cellular Landscape
Statewide Analysis Of Cellular Tower Distribution Throughout The United States
In our analysis of cellular tower coverage across the United States, we delve into a rich dataset provided by the Federal Communications Commission (FCC). This dataset meticulously records cellular tower locations nationwide, offering crucial insights into the nation’s telecommunications infrastructure. While primarily intended for informational use in GIS systems and general planning, and not for precise engineering or legal delineation of FCC market boundaries, the data still provides a valuable snapshot of cellular network distribution. In the first interactive map and graph we look at the total distribution of Cell towers by state. The distribution of cell towers is uneven, with high concentrations in certain areas, such as the West Coast and Eastern Seaboard, and relatively sparse distributions in the central states. The graph shows that Texas, California, and Florida possess the highest numbers of cell towers, aligning with their status as the three most populous states in the USA. This suggests a correlation between population density and the number of cell towers needed to serve the residents.
= gpd.read_file("./Data/Cellular_Towers_in_the_United_States2.csv",vcrs='4326')
cell_towers_gdf = gpd.read_file('./Data/states.json', crs='4326')
state_boundaries_gdf
'londec'] = pd.to_numeric(cell_towers_gdf['londec'], errors='coerce')
cell_towers_gdf['latdec'] = pd.to_numeric(cell_towers_gdf['latdec'], errors='coerce') cell_towers_gdf[
= state_boundaries_gdf.total_bounds
bounds = 0
x_range_padding = (bounds[3] - bounds[1]) * 0.05
y_range_padding
= (bounds[0] - x_range_padding, bounds[2] + x_range_padding)
x_range = (bounds[1] - y_range_padding, bounds[3] + y_range_padding)
y_range
= 1200
plot_width = int(plot_width * (y_range[1] - y_range[0]) / (x_range[1] - x_range[0]))
plot_height
if cell_towers_gdf.crs is None:
=4326, inplace=True)
cell_towers_gdf.set_crs(epsg
= cell_towers_gdf.to_crs(state_boundaries_gdf.crs)
cell_towers_gdf
'LocState'] = cell_towers_gdf['LocState'].astype('category')
cell_towers_gdf[
= ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_range, y_range=y_range)
canvas
= canvas.points(cell_towers_gdf, 'londec', 'latdec', ds.count_cat('LocState'))
agg
= {str(state): color for state, color in zip(cell_towers_gdf['LocState'].cat.categories, cc.b_glasbey_category10)}
color_key
= tf.spread(agg, px=1)
spread_plot
= tf.set_background(tf.shade(agg, color_key=color_key), "black")
plot plot
= state_boundaries_gdf[~state_boundaries_gdf['name'].isin(['Alaska', 'Hawaii'])]
state_boundaries_gdf
= state_boundaries_gdf.total_bounds
bounds
= (bounds[2] - bounds[0]) * 0.05
x_range_padding = (bounds[3] - bounds[1]) * 0.05
y_range_padding
= (bounds[0] - x_range_padding, bounds[2] + x_range_padding)
x_range = (bounds[1] - y_range_padding, bounds[3] + y_range_padding)
y_range
= 1200
plot_width = int(plot_width * (y_range[1] - y_range[0]) / (x_range[1] - x_range[0]))
plot_height
if cell_towers_gdf.crs is None:
=4326, inplace=True)
cell_towers_gdf.set_crs(epsg
= cell_towers_gdf.to_crs(state_boundaries_gdf.crs)
cell_towers_gdf
'LocState'] = cell_towers_gdf['LocState'].astype('category')
cell_towers_gdf[
= ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_range, y_range=y_range)
canvas
= canvas.points(cell_towers_gdf, 'londec', 'latdec', ds.count_cat('LocState'))
agg
= {str(state): color for state, color in zip(cell_towers_gdf['LocState'].cat.categories, cc.b_glasbey_category10)}
color_key
= tf.spread(plot, px=1)
spread_plot
= tf.set_background(tf.shade(agg, color_key=color_key), "black")
plot plot
= gpd.read_file('./Data/states.json')
state_boundaries_gdf
= state_boundaries_gdf[~state_boundaries_gdf['name'].isin(['Alaska', 'Hawaii'])]
state_boundaries_gdf
= state_boundaries_gdf.total_bounds
bounds
= (bounds[2] - bounds[0]) * 0.05
x_range_padding = (bounds[3] - bounds[1]) * 0.05
y_range_padding
= (bounds[0] - x_range_padding, bounds[2] + x_range_padding)
x_range = (bounds[1] - y_range_padding, bounds[3] + y_range_padding)
y_range
= 1200
plot_width = int(plot_width * (y_range[1] - y_range[0]) / (x_range[1] - x_range[0]))
plot_height
if cell_towers_gdf.crs is None:
=4326, inplace=True)
cell_towers_gdf.set_crs(epsg
= cell_towers_gdf.to_crs(state_boundaries_gdf.crs)
cell_towers_gdf
'LocState'] = cell_towers_gdf['LocState'].astype('category')
cell_towers_gdf[
= ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_range, y_range=y_range)
canvas
= canvas.points(cell_towers_gdf, 'londec', 'latdec', ds.count_cat('LocState'))
agg
= {str(state): color for state, color in zip(cell_towers_gdf['LocState'].cat.categories, cc.b_glasbey_category10)}
color_key
= tf.shade(agg, color_key=color_key)
plot
= plot.to_pil()
img
= 100
dpi = plot_width / dpi
fig_width_in = plot_height / dpi
fig_height_in
= plt.subplots(figsize=(20,17))
fig, ax 'white')
fig.patch.set_facecolor(
=ax, facecolor="none", edgecolor="black", linewidth=0.5, alpha=0.3)
state_boundaries_gdf.plot(ax
=[x_range[0], x_range[1], y_range[0], y_range[1]])
ax.imshow(img, extent
'white')
ax.set_facecolor('off')
ax.axis(
plt.show()
= cell_towers_gdf.groupby('LocState').size().reset_index(name='Count')
state_counts
= alt.Chart(state_counts).mark_bar().encode(
bar_chart =alt.X('Count:Q', title='Number of Cell Towers'),
x=alt.Y('LocState:N', title='State', sort='-x'),
y=alt.Color('Count:Q', scale=alt.Scale(scheme='plasma')),
color=['LocState:N', 'Count:Q']
tooltip
).interactive()
= bar_chart.configure_view(
styled_chart =0,
strokeWidth='#000'
fill
).properties(='Number of Cell Towers per State',
title=600,
width=800
height
)
styled_chart
= ['Alaska', 'Hawaii']
exclude_states = state_boundaries_gdf[~state_boundaries_gdf['name'].isin(exclude_states)]
state_boundaries_gdf
if cell_towers_gdf.crs is None:
=4326, inplace=True)
cell_towers_gdf.set_crs(epsg
= ds.Canvas(plot_width=900, plot_height=600, x_range=(-125, -67), y_range=(24, 50)) # Bounds for the contiguous US
canvas
= canvas.points(cell_towers_gdf, 'londec', 'latdec', ds.count())
agg
= tf.shade(agg, cmap=cc.fire, how='log')
plot
= plot.to_pil()
img
= plt.subplots(figsize=(20, 17))
fig, ax 'white')
fig.patch.set_facecolor(
=ax, facecolor="none", edgecolor="black", linewidth=0.5, alpha=0.3)
state_boundaries_gdf.plot(ax
=[-125, -67, 24, 50])
ax.imshow(img, extent
'white')
ax.set_facecolor('off')
ax.axis(
plt.show()
Insights Into United States Cell Tower Landscape By Licensee
This data visualization provides insight into the top ten companies with the most cell towers in the United States, revealing AT&T and Verizon a providers with the largest counts. Lesser yet substantial numbers are held by companies like ALLTEL Corporation and Celico Partnership. The mapping of these towers highlights the extensive reach of these companies, showing a strategic placement that shapes the competitive telecom landscape. The spatial arrangement of these towers reflects a strategy employed by these telecommunications giants, intertwining urban centers, suburban expanses, and rural landscapes. Further, this placement reflects a comprehensive network coverage strategy, ensuring connectivity across diverse regions. The data analysis highlights the telecom industry’s evolving nature, with emerging players like Celico Partnership and ALLTEL Corporation carving out significant niches. Despite their smaller tower counts, their strategically placed infrastructure reveals a targeted approach to meeting regional demands. In summary, highlighting both industry leaders’ dominance and the network of emerging contenders, this visualization emphasizes the dynamic that shapes a competitive environment adapting to consumer needs and technological advancements.
= cell_towers_gdf.groupby('Licensee').size().reset_index(name='Count')
licensee_counts
= licensee_counts.sort_values(by='Count', ascending=False).head(10)['Licensee'].tolist()
top_licensees
= ['Licensee', 'LatSec', 'LonSec', 'LocState']
columns_to_keep
= cell_towers_gdf[columns_to_keep]
cell_towers_gdf_trimmed
'londec'] = pd.to_numeric(cell_towers_gdf['londec'], errors='coerce')
cell_towers_gdf['latdec'] = pd.to_numeric(cell_towers_gdf['latdec'], errors='coerce') cell_towers_gdf[
= gpd.read_file('./Data/states.json')
state_boundaries_gdf = state_boundaries_gdf.to_crs(epsg=4326)
state_boundaries_gdf
= ['Alaska', 'Hawaii']
exclude_states = state_boundaries_gdf[~state_boundaries_gdf['name'].isin(exclude_states)]
state_boundaries_gdf
if cell_towers_gdf.crs is None:
=4326, inplace=True)
cell_towers_gdf.set_crs(epsg
= cell_towers_gdf.groupby('Licensee').size().reset_index(name='Count')
licensee_counts = licensee_counts.sort_values(by='Count', ascending=False).head(10)['Licensee'].tolist()
top_licensees
= cell_towers_gdf[cell_towers_gdf['Licensee'].isin(top_licensees)]
cell_towers_gdf
'Licensee'] = cell_towers_gdf['Licensee'].astype('category')
cell_towers_gdf[
= ds.Canvas(plot_width=1200, plot_height=800, x_range=(-125, -67), y_range=(24, 50)) # Increase plot_width and plot_height
canvas
= canvas.points(cell_towers_gdf, 'londec', 'latdec', ds.count_cat('Licensee'))
agg
*= 1
agg
= plt.cm.get_cmap('plasma', len(top_licensees))
plasma_cmap = {licensee: matplotlib.colors.to_hex(plasma_cmap(i)) for i, licensee in enumerate(top_licensees)}
licensee_color
= tf.shade(agg, color_key=licensee_color)
plot
= tf.spread(plot, px=1)
spread_plot
= plt.subplots(figsize=(22, 14), facecolor='black')
fig, ax
=ax, facecolor="none", edgecolor="white", linewidth=0.5, alpha=0.3)
state_boundaries_gdf.plot(ax
=[-125, -67, 24, 50], alpha=0.8) # Reduce alpha for better visibility
ax.imshow(spread_plot.to_pil(), extent
'black')
ax.set_facecolor('off')
ax.axis(
= [f"{i + 1}. {licensee}" for i, licensee in enumerate(top_licensees)]
legend_labels = [Patch(color=licensee_color[licensee]) for licensee in top_licensees]
legend_patches = ax.legend(legend_patches, legend_labels, loc='lower right', title='Top Licensees', title_fontsize='large', facecolor='black')
legend True)
legend.set_frame_on('black')
legend.get_frame().set_facecolor('white')
legend.get_frame().set_edgecolor(for text in legend.get_texts():
'white')
text.set_color(
plt.show()
= cell_towers_gdf.groupby('Licensee').size().reset_index(name='Count')
licensee_counts
= licensee_counts.sort_values(by='Count', ascending=False).head(10)
top_licensees
= {k: licensee_color[k] for k in top_licensees['Licensee'] if k in licensee_color}
top_licensee_colors
= alt.Chart(top_licensees).mark_bar().encode(
chart =alt.X('Count:Q', title='Number of Cell Towers'),
x=alt.Y('Licensee:N', title='Licensee', sort='-x'),
y=alt.Color('Licensee:N', scale=alt.Scale(domain=list(top_licensee_colors.keys()), range=list(top_licensee_colors.values()))),
color=['Licensee:N', 'Count:Q']
tooltip
).properties(=300 # Adjust the height as needed
height
).configure_view(=600,
continuousWidth='#000000' # Black background
fill
).interactive()
chart
= cell_towers_gdf.groupby('Licensee').size().reset_index(name='Count')
licensee_counts
= licensee_counts.sort_values(by='Count', ascending=False).head(10)['Licensee']
top_licensees
'LicenseeAdjusted'] = cell_towers_gdf['Licensee'].where(cell_towers_gdf['Licensee'].isin(top_licensees), 'Other')
cell_towers_gdf[
= cell_towers_gdf.groupby(['LocState', 'LicenseeAdjusted']).size().reset_index(name='Count')
state_licensee_counts_adjusted
= {**licensee_color, 'Other': 'grey'}
licensee_colors_adjusted = top_licensees.tolist() + ['Other']
unique_licensees_adjusted
= alt.Chart(state_licensee_counts_adjusted).mark_bar().encode(
bar_chart =alt.X('Count:Q', title='Number of Cell Towers'),
x=alt.Y('LocState:N', title='State', sort='-x'),
y=alt.Color('LicenseeAdjusted:N', legend=alt.Legend(title="Licensee"),
color=alt.Scale(domain=unique_licensees_adjusted, range=[licensee_colors_adjusted[lic] for lic in unique_licensees_adjusted])),
scale=['LocState:N', 'LicenseeAdjusted:N', 'Count:Q']
tooltip
).interactive()
= bar_chart.configure_view(
styled_chart2 =0,
strokeWidth='#000'
fill
).properties(='Number of Cell Towers per State by Licensee',
title=600,
width=800
height
) styled_chart2