Old Provices of Vietnam (before July 1, 2025)

First, we are going to use the previous administrative data for Vietnam available at GADM.

import geopandas as gpd
import pandas as pd

geojson_file = 'https://geodata.ucdavis.edu/gadm/gadm4.1/json/gadm41_VNM_1.json.zip'

# Read in the GeoJSON file
gdata = gpd.read_file(geojson_file)
# plot
gdata.plot(column="NAME_1")

When you open the dataframe, you will see 63 rows, corresponding to 63 provinces.

print(gdata[['NAME_1','geometry']])
##            NAME_1                                           geometry
## 0         AnGiang  MULTIPOLYGON (((105.5486 10.4295, 105.5495 10....
## 1   BàRịa-VũngTàu  MULTIPOLYGON (((107.0901 10.324, 107.0889 10.3...
## 2        BắcGiang  MULTIPOLYGON (((106.2838 21.1323, 106.2734 21....
## 3          BắcKạn  MULTIPOLYGON (((105.8724 21.8558, 105.8629 21....
## 4         BạcLiêu  MULTIPOLYGON (((105.4244 9.0213, 105.4164 9.01...
## ..            ...                                                ...
## 58        TràVinh  MULTIPOLYGON (((106.4135 9.5292, 106.4088 9.53...
## 59     TuyênQuang  MULTIPOLYGON (((105.5104 21.6047, 105.5141 21....
## 60       VĩnhLong  MULTIPOLYGON (((106.0632 9.9475, 106.0627 9.94...
## 61       VĩnhPhúc  MULTIPOLYGON (((105.5713 21.1615, 105.5336 21....
## 62         YênBái  MULTIPOLYGON (((104.7946 21.3352, 104.7908 21....
## 
## [63 rows x 2 columns]

Now, we are going to see the names of all provinces

print(gdata['NAME_1'].unique())
## ['AnGiang' 'BàRịa-VũngTàu' 'BắcGiang' 'BắcKạn' 'BạcLiêu' 'BắcNinh'
##  'BếnTre' 'BìnhĐịnh' 'BìnhDương' 'BìnhPhước' 'BìnhThuận' 'CàMau' 'CầnThơ'
##  'CaoBằng' 'ĐàNẵng' 'ĐắkLắk' 'ĐắkNông' 'ĐiệnBiên' 'ĐồngNai' 'ĐồngTháp'
##  'GiaLai' 'HàGiang' 'HàNam' 'HàNội' 'HàTĩnh' 'HảiDương' 'HảiPhòng'
##  'HậuGiang' 'HồChíMinh' 'HoàBình' 'HưngYên' 'KhánhHòa' 'KiênGiang'
##  'KonTum' 'LaiChâu' 'LâmĐồng' 'LạngSơn' 'LàoCai' 'LongAn' 'NamĐịnh'
##  'NghệAn' 'NinhBình' 'NinhThuận' 'PhúThọ' 'PhúYên' 'QuảngBình' 'QuảngNam'
##  'QuảngNgãi' 'QuảngNinh' 'QuảngTrị' 'SócTrăng' 'SơnLa' 'TâyNinh'
##  'TháiBình' 'TháiNguyên' 'ThanhHóa' 'ThừaThiênHuế' 'TiềnGiang' 'TràVinh'
##  'TuyênQuang' 'VĩnhLong' 'VĩnhPhúc' 'YênBái']

The New Province System (Post July 1, 2025)

First, I need to define a list of all the merges. This has to be done manually. Note that the final name of the merged province must be put as the first element of a list.

# Define all the merges
merges = [
    ["TuyênQuang", "HàGiang"],
    ["LàoCai", "YênBái"],
    ["TháiNguyên", "BắcKạn"],
    ["PhúThọ", "VĩnhPhúc", "HoàBình"],
    ["BắcNinh", "BắcGiang"],
    ["HưngYên", "TháiBình"],
    ["HảiPhòng", "HảiDương"],
    ["NinhBình", "NamĐịnh", "HàNam"],
    ["QuảngBình", "QuảngTrị"],
    ["ĐàNẵng", "QuảngNam"],
    ["QuảngNgãi", "KonTum"],
    ["GiaLai", "BìnhĐịnh"],
    ["KhánhHòa", "NinhThuận"],
    ["LâmĐồng", "BìnhThuận", "ĐắkNông"],
    ["ĐắkLắk", "PhúYên"],
    ["HồChíMinh", "BìnhDương", "BàRịa-VũngTàu"],
    ["ĐồngNai", "BìnhPhước"],
    ["TâyNinh", "LongAn"],
    ["CầnThơ", "SócTrăng", "HậuGiang"],
    ["VĩnhLong", "BếnTre", "TràVinh"],
    ["ĐồngTháp", "TiềnGiang"],
    ["CàMau", "BạcLiêu"],
    ["AnGiang", "KiênGiang"]
]

The merge procedure is then done as follows.

We will loop through each list in the merged list. In each loop, here is what we’re going to do: - subset the rows that include the provinces to be merged - merge their geometry using union_all() function - create a new province (row) with new NAME_1 and geometry, with origins include the original provices before the merge - delete the old rows from the old dataset and insert the new row.

# Start with a copy of the original
gdata_new = gdata.copy()

# Loop through each merge group
for group in merges:
    # Subset the rows to be merged
    to_merge = gdata_new[gdata_new['NAME_1'].isin(group)]
    
    # Merge the geometries
    merged_geom = to_merge.union_all()
    
    # Create new merged row with first name in group
    merged_row = gpd.GeoDataFrame({
        'NAME_1': [group[0]],
        'geometry': [merged_geom],
        'origins':[group]
    }, 
    crs=gdata_new.crs
    )
    
    # Drop old rows and append new one
    gdata_new = gdata_new[~gdata_new['NAME_1'].isin(group)].copy()
    gdata_new = pd.concat([gdata_new, merged_row], ignore_index=True)

# Replace NaN with NAME_1
gdata_new['origins'] = gdata_new['origins'].fillna(gdata_new['NAME_1'])
print(gdata_new[['NAME_1','origins']])
##           NAME_1                                origins
## 0        CaoBằng                                CaoBằng
## 1       ĐiệnBiên                               ĐiệnBiên
## 2          HàNội                                  HàNội
## 3         HàTĩnh                                 HàTĩnh
## 4        LaiChâu                                LaiChâu
## 5        LạngSơn                                LạngSơn
## 6         NghệAn                                 NghệAn
## 7      QuảngNinh                              QuảngNinh
## 8          SơnLa                                  SơnLa
## 9       ThanhHóa                               ThanhHóa
## 10  ThừaThiênHuế                           ThừaThiênHuế
## 11    TuyênQuang                  [TuyênQuang, HàGiang]
## 12        LàoCai                       [LàoCai, YênBái]
## 13    TháiNguyên                   [TháiNguyên, BắcKạn]
## 14        PhúThọ            [PhúThọ, VĩnhPhúc, HoàBình]
## 15       BắcNinh                    [BắcNinh, BắcGiang]
## 16       HưngYên                    [HưngYên, TháiBình]
## 17      HảiPhòng                   [HảiPhòng, HảiDương]
## 18      NinhBình             [NinhBình, NamĐịnh, HàNam]
## 19     QuảngBình                  [QuảngBình, QuảngTrị]
## 20        ĐàNẵng                     [ĐàNẵng, QuảngNam]
## 21     QuảngNgãi                    [QuảngNgãi, KonTum]
## 22        GiaLai                     [GiaLai, BìnhĐịnh]
## 23      KhánhHòa                  [KhánhHòa, NinhThuận]
## 24       LâmĐồng          [LâmĐồng, BìnhThuận, ĐắkNông]
## 25        ĐắkLắk                       [ĐắkLắk, PhúYên]
## 26     HồChíMinh  [HồChíMinh, BìnhDương, BàRịa-VũngTàu]
## 27       ĐồngNai                   [ĐồngNai, BìnhPhước]
## 28       TâyNinh                      [TâyNinh, LongAn]
## 29        CầnThơ           [CầnThơ, SócTrăng, HậuGiang]
## 30      VĩnhLong            [VĩnhLong, BếnTre, TràVinh]
## 31      ĐồngTháp                  [ĐồngTháp, TiềnGiang]
## 32         CàMau                       [CàMau, BạcLiêu]
## 33       AnGiang                   [AnGiang, KiênGiang]

Printing out gdata_new, you will see that it has only 34 rows, which corresponds to the new set of provinces. Those with NaN in column origins indicate provinces without any changes, so we just set their value to be the same as ‘NAME_1’.

Interactive Plot

We can also use plotly to show an interactive plot.

import plotly.express as px
import pyproj

fig = px.choropleth(
    gdata_new,
    geojson=gdata_new.geometry,
    locations=gdata_new.index,
    color="NAME_1",
    hover_data=["origins"],
    labels={"NAME_1": "Province"}
)

fig.update_geos(fitbounds="locations", visible=False)

# testing on Rstudio
# (python chunk) fig.write_html("new_map.html", full_html=False, include_plotlyjs='cdn', auto_open=False)
# (R chunk) htmltools::includeHTML("new_map.html")