Alfamart vs Indomart dominance in Jakarta
Alfamart and Indomart are two of the most popular convenience stores in Indonesia.
For thought experiment, lets try to explore the data that we can get from OpenStreetMap about it. Do note though that OSM data is not always complete and up to date, so the results might not be 100% accurate. If you notice specific place are missing, do try to add them on OSM!
Pull list of minimarket
To do this, we are using osmnx library, which simplify the query process to the OpenStreetMap database, and we got a tidy GeoDataFrame ready to process further.
1import osmnx as ox
2
3# Define the area - using Jakarta's bounding box
4bbox = (106.6894, -6.3697, 106.9755, -6.1297) # (west, south, east, north)
5
6alfamart_tags = {'brand': 'Alfamart'}
7alfamart = ox.features.features_from_bbox(bbox, tags=alfamart_tags)
8
9indomaret_tags = {'brand': 'Indomaret'}
10indomaret = ox.features.features_from_bbox(bbox, tags=indomaret_tags)
11
12# Print the number of Alfamart and Indomaret locations
13print(f"Number of Alfamart: {len(alfamart)}")
14print(f"Number of Indomaret: {len(indomaret)}")
Number of Alfamart: 305 Number of Indomaret: 413
1import pandas as pd
2from shapely.geometry import Point
3import geopandas as gpd
4
5alfamart_points = gpd.GeoDataFrame(
6 geometry=[Point(geom.centroid.xy) for geom in alfamart.geometry],
7 crs=alfamart.crs
8)
9alfamart_points['type'] = 'Alfamart'
10
11indomaret_points = gpd.GeoDataFrame(
12 geometry=[Point(geom.centroid.xy) for geom in indomaret.geometry],
13 crs=indomaret.crs
14)
15indomaret_points['type'] = 'Indomaret'
16
17# Combine the data
18all_points = pd.concat([alfamart_points, indomaret_points])
Plotting the data
Now, we can plot the data on a map. We will use contextily
to add the basemap, and matplotlib
to plot the data.
Show code (71 lines)
1# Create a map using contextily and matplotlib
2import contextily as ctx
3import matplotlib.pyplot as plt
4
5all_points_gdf = gpd.GeoDataFrame(all_points)
6# Create figure and axis
7fig, ax = plt.subplots(figsize=(15, 15))
8
9# Ensure data is in Web Mercator projection (EPSG:3857) for contextily
10all_points_gdf = all_points_gdf.to_crs(epsg=3857)
11
12# Calculate grid size for zoom level 11 (in meters since we're in Web Mercator)
13grid_size = 1_000
14
15# Group points into grid cells
16all_points_gdf['grid_x'] = (all_points_gdf.geometry.x // grid_size) * grid_size
17all_points_gdf['grid_y'] = (all_points_gdf.geometry.y // grid_size) * grid_size
18
19# Count stores by type in each grid cell
20grid_counts = all_points_gdf.groupby(['grid_x', 'grid_y', 'type']).size().unstack(fill_value=0)
21grid_counts['dominant'] = grid_counts.idxmax(axis=1)
22grid_counts['difference'] = abs(grid_counts['Alfamart'] - grid_counts['Indomaret'])
23
24# Create rectangles for each grid cell
25for idx, row in grid_counts.iterrows():
26 grid_x, grid_y = idx
27
28 # Color intensity based on dominance
29 opacity = min(row['difference'] / 5, 0.8) # Cap at 0.8 opacity
30
31 # Red for Alfamart dominance, Blue for Indomaret
32 color = 'red' if row['dominant'] == 'Alfamart' else 'blue'
33
34 # Create rectangle
35 rect = plt.Rectangle(
36 (grid_x, grid_y),
37 grid_size,
38 grid_size,
39 facecolor=color,
40 alpha=opacity,
41 edgecolor='none'
42 )
43 ax.add_patch(rect)
44
45# Set map bounds to Jakarta area (converted to Web Mercator coordinates)
46# Set bounds based on the data points with some padding
47x_min = all_points_gdf.geometry.x.min()
48x_max = all_points_gdf.geometry.x.max()
49y_min = all_points_gdf.geometry.y.min()
50y_max = all_points_gdf.geometry.y.max()
51x_padding = (x_max - x_min) * 0.1
52y_padding = (y_max - y_min) * 0.1
53
54ax.set_xlim([x_min - x_padding, x_max + x_padding])
55ax.set_ylim([y_min - y_padding, y_max + y_padding])
56
57ctx.add_basemap(
58 ax,
59 source=ctx.providers.CartoDB.Positron,
60 crs=all_points_gdf.crs
61)
62
63legend_elements = [
64 plt.Rectangle((0,0), 1, 1, facecolor='red', alpha=0.6, label='Alfamart Dominant'),
65 plt.Rectangle((0,0), 1, 1, facecolor='blue', alpha=0.6, label='Indomaret Dominant')
66]
67ax.legend(handles=legend_elements, loc='upper right')
68ax.set_axis_off()
69
70plt.title('Minimarket Chain Dominance in Jakarta\nGrid size: ~1km', pad=20)
71plt.show()
🗒️ Download (alfamart-vs-indomart.ipynb)