When calculating adjacency, there is sometimes a distinction made between queen adjacency and rook adjacency.
Queen vs. Rook Adjacency
For a given shape X, the Queen adjacent shapes are all the shapes that touch X. For a given shape X, the Rook adjacent shapes are all the shapes that touch X at more than just one particular point.
The two types of adjacencies are named as such as a nod to the movement of the chess pieces. Rooks can only move up or down or left to right, whereas queens can move up, down, left, right or any direction diagonally. From a given square X on a chess board, the rook adjacent squares would be those that border square X and could be traveled to by a rook, and likewise for the queen adjacency squares.

Calculating the Two
The shapefile adjacency code I shared here, which uses a buffer, will only return the queen adjacency.
The new code, shared below, which does not user a buffer, allows a user to specify whether they want point adjacencies to be return or not. This occurs by intersecting the shapefile with itself, without a buffer and then, depending on the “include_point_adjacency” parameter, filtering out the intersection that are just of Point geometry type. These are the point intersections
import pandas as pd
import geopandas as gp
from collections import defaultdict
def calculate_adjacency(gdf, unique_col):
'''
Code that takes a geodataframe and returns two dataframes: the first with rook adjacencies and the second with queen adjacencies.
Both dataframes have two columns the first column with the unique column values, and the second column with a list of adjacent geometries, listed by their unique column value.
'''
# Confirm that unique_col is actually unique
if not(max(gdf[unique_col].value_counts(dropna = False) == 1)):
raise ValueError("Non-unique column provided")
# Intersected the GeoDataFrame with the buffer with the original GeoDataFrame
all_intersections = gp.overlay(gdf, gdf, how = "intersection", keep_geom_type = False)
# Filter out self-intersections
filtered_intersections = all_intersections[all_intersections[unique_col+"_1"]!=all_intersections[unique_col+"_2"]]
# Separate out point intersections
point_intersections = filtered_intersections[filtered_intersections.geom_type == "Point"]
non_point_intersections = filtered_intersections[filtered_intersections.geom_type != "Point"]
# Define a tuple of zips of the unique_col pairs present in the non-point intersections
non_point_intersections_tuples = tuple(zip(non_point_intersections[unique_col+"_1"], non_point_intersections[unique_col+"_2"]))
# Define a dictionary that will map from a unique_col value to a list of other unique_cols it is adjacent to
rook_dict = defaultdict(list)
# Iterate over the tuples
for val in non_point_intersections_tuples:
rook_dict[val[0]].append(val[1])
# Some shapes will only intersect with themselves and not be added to the above
not_added = list(set(gdf[unique_col]).difference(set(rook_dict.keys())))
for val in not_added:
# For each of these, add a blank list to the dictionary
rook_dict[val] = []
# Create DataFrame of rook intersections
df_rook = pd.DataFrame()
df_rook['GEOID20'] = rook_dict.keys()
df_rook["ADJ_GEOMS"] = rook_dict.values()
# Make a copy of the dictionary so we can add the point intersections
queen_dict = {key: value[:] for key, value in rook_dict.items()}
# Define a tuple of zips of the unique_col pairs present in the point intersections
point_intersection_tuples = tuple(zip(point_intersections[unique_col+"_1"], point_intersections[unique_col+"_2"]))
for val in point_intersection_tuples:
queen_dict[val[0]].append(val[1])
# Create DataFrame of queen intersections
df_queen = pd.DataFrame()
df_queen['GEOID20'] = queen_dict.keys()
df_queen["ADJ_GEOMS"] = queen_dict.values()
return df_rook, df_queen
Comparing the Code

