Using GeoPandas to Build Updated Philippine Regions Shape File in Python

In a previous post that took a look at CPI inflation rates by region, I sort of bemoaned my inability to find up-to-date Philippine shape files that already included the newly-formed Negros Island Region in most open GIS databases. As a result, we simply had to make do with what was available and plotted the regional inflation rates for the month of June 2016 as if the NIR didn’t exist. Well, it turns out, I was a complete idiot for being unable to figure out a workaround to the problem. The solution just hit me all of a sudden last week in the middle of a lucid dream, and is embarrassingly so simple that I’m still kicking myself at time of writing for not having thought of it sooner.

In this post, I’ll try to go over how I went about generating a (hopefully) up-to-date and accurate shape file for the Philippine administrative regions using the very handy Geopandas package in Python. I’m by no means an expert in GIS data analysis (in fact, I’m not an expert at anything), but I’m sharing my approach and results anyway for anyone interested.

An 18th century map of the Philippine Islands, dated 1774. (Image from Wikimedia Commons)
An 18th century map of the Philippine Islands, dated 1774. (Image from Wikimedia Commons)

What follows is an edited and repackaged version of the Jupyter notebook I’ve put together to generate the shape file. Along with the complete code, the dataset (the original shape files) and other related stuff can be found in this GitHub repository. Or, if you just want to skip all that and simply download the new shape file, just click here to access the zip file. Otherwise, read on.

Contents

  1. Some Preliminaries
  2. Reading the Shape Files and PSGG Code File
  3. Previewing the Data
  4. Matching and Inserting the PSGG Codes
  5. Plotting the Old Regional Shape File
  6. Updating the Regional Boundaries
  7. Plotting the New Shapes
  8. Generating the New Shape File
  9. The Takeaway

Some Preliminaries

This notebook uses Geopandas and Shapely to generate an updated shape file for the Philippine map, reflecting changes in regional boundaries as a result of the creation of the Negros Island Region (NIR) in 2015. As I’ve pointed out in a previous notebook, I’ve been unable to find open Philippine GIS data sources that already include this adjustment. So, let’s do the next best thing and try to create it ourselves.

To make sure we’re on the same page, the project directory for this notebook is initially organized as follows:

root
|   .gitignore
|   psgg_codes.csv
|   README.md
|   requirements.txt
|   updating-ph-regions-map.ipynb
|   
+---Original
|       PHL_adm1.dbf
|       PHL_adm1.shp
|       PHL_adm1.shx
|       Regions.dbf
|       Regions.shp
|       Regions.shx
|       
\---Updated

The original shape files used here have been downloaded from the following:

IMPORTANT: Because of the license terms accompanying the GIS datafiles downloaded from the GADM Database, I did not push those files into this repository. So, in order to have all the needed shape files to run this notebook, please do the following:

  1. Go to the GADM download page.
  2. Choose country data for the Philippines and hit download.
  3. Unzip the downloaded zip file.
  4. Copy the files PHL_adm1.dbf, PHL_adm1.shp, and PHL_adm1.shx into the directory named Original of this project.

I’ve also created a csv file containing the 18 regions’ respective PSGG codes using data from a PSA Provinces Summary (2016 file. We’ll use this later on for easier indexing.

To run this notebook, you need to have Geopandas and Shapely properly installed. Please see the README file in the root of this notebook’s repo for the full list of dependencies, and also go through this very helpful installation tutorial by Jeff Boeing if you’re running this on a Windows machine.

We’ll also be using another third-party package called fuzzywuzzy to help us with matching identical text in our region names. This package can be installed with either pip or conda install.

Now for the imports:

% matplotlib inline
import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd
from fuzzywuzzy import process

Reading the Shape Files and PSGG Code File

As shown in the above directory tree, the shape files are located in the Original directory, while the PSGG file is in the root directory.

regions = gpd.GeoDataFrame.from_file('Original/Regions.shp')
provinces = gpd.GeoDataFrame.from_file('Original/PHL_adm1.shp')
psgg_code = pd.read_csv('psgg_codes.csv', dtype=object)

We read our shape files as GeoDataFrame objects, while the PSGG code file is read as a normal pandas DataFrame object. The main difference between these two types is that a GeoDataFrame object must contain at least one geometry column which contains point, polygon, or multi-polygon data that in turn define the shape of the region or area associated with it.

Previewing the Data

Let’s see if everything we’ve loaded so far checks out.

regions.shape

The above line of code returns a tuple (17, 2) which tells us that the regions GeoDataFrame is comprised of 17 rows and 2 columns. Let’s see what it really looks like:

regions

This should yield the below table:

REGION geometry
0 Autonomous Region of Muslim Mindanao (ARMM) (POLYGON ((119.4669418334962 4.586939811706523…
1 Bicol Region (Region V) (POLYGON ((122.9841690063476 11.71055984497071…
2 CALABARZON (Region IV-A) (POLYGON ((125.2216567993167 10.43443965911868…
3 Cagayan Valley (Region II) (POLYGON ((122.4703979492188 16.91995048522949…
4 Caraga (Region XIII) (POLYGON ((126.417503356934 7.964169979095546,…
5 Central Luzon (Region III) (POLYGON ((120.6236267089844 14.36787986755386…
6 Central Visayas (Region VII) (POLYGON ((123.2711105346679 9.084759712219269…
7 Cordillera Administrative Region (CAR) POLYGON ((121.3767929077149 17.95472908020015,…
8 Davao Region (Region XI) (POLYGON ((125.3977813720705 5.435830116271932…
9 Eastern Visayas (Region VIII) (POLYGON ((125.0736083984376 9.89472007751475,…
10 Ilocos Region (Region I) (POLYGON ((119.8650512695319 15.81169986724875…
11 MIMAROPA (Region IV-B) (POLYGON ((117.3138885498046 7.514170169830424…
12 Metropolitan Manila (POLYGON ((120.9797210693363 14.49305534362795…
13 Northern Mindanao (Region X) (POLYGON ((123.6219329833985 7.828589916229499…
14 SOCCSKSARGEN (Region XII) POLYGON ((124.5379867553711 7.681869983673105,…
15 Western Visayas (Region VI) (POLYGON ((122.4352188110355 9.643819808960162…
16 Zamboanga Peninsula (Region IX) (POLYGON ((122.0622329711914 6.872779846191344…

As expected, the regions GeoDataFrame (which we’ll refer to as GDF from this point on) contains geometry data for 17 Philippine regions and doesn’t yet include data for the NIR.

Now let’s check out the provinces GDF. We start with its shape:

provinces.shape

This gives (81, 13). So it’s a bit larger than the regions GDF not only in the number of rows but in terms of the number of columns as well.

Rather than view what the entire object looks like, let’s see if we can focus instead on only a few columns and possibly a few rows to preview.

provinces.columns

This gives us the following result:

Index([    u'CCA_1',     u'CCN_1', u'ENGTYPE_1',    u'HASC_1',      u'ID_0',
                u'ID_1',       u'ISO',    u'NAME_0',    u'NAME_1', u'NL_NAME_1',
              u'TYPE_1', u'VARNAME_1',  u'geometry'],
          dtype='object')

I’m not sure what those other column names are for, but I think the columns NAME_1 and geometry should be enough for our purposes. So let’s just focus on these fields to preview the provinces GDF.

cols = ['NAME_1', 'geometry']
provinces.head()[cols]

The preceding block of code gives the following table:

NAME_1 geometry
0 Abra POLYGON ((120.9679489135743 17.95705986022955,…
1 Agusan del Norte POLYGON ((125.5772399902345 9.456789970397892,…
2 Agusan del Sur POLYGON ((125.9108734130859 8.856249809265194,…
3 Aklan (POLYGON ((122.4366683959961 11.59832954406733…
4 Albay (POLYGON ((123.2876434326172 13.04922962188726…

The provinces GDF contains geometry and other data for the 81 Philippine provinces. We previewed this one by specifying only the NAME_1 and geometry columns along with the .head() call. Let’s see if it has the two provinces we’re looking for.

neg_prov = provinces.loc[
    provinces.NAME_1.str.contains(r'Negros'), cols
]
neg_prov

Above, we use pandas’s (and, by extension, GeoPandas’s) built-in tools for working with text data to select rows in provinces that contain the string “Negros”. This gives the following:

NAME_1 geometry
50 Negros Occidental (POLYGON ((122.4352188110356 9.643819808959961…
51 Negros Oriental (POLYGON ((123.271110534668 9.084759712219238,…

And sure enough, it does have data for the two Negros provinces. Now, onto the updated PSGG codes:

psgg_code.shape

The preceding line of code returns (18, 2). And if we try to see what the pscc_code looks like,

psgg_code

… we find:

psgg_code region
0 18 NIR (Negros Island Region)
1 13 NCR (National Capital Region)
2 14 CAR (Cordillera Administrative Region)
3 01 Region I (Ilocos Region)
4 02 Region II (Cagayan Valley)
5 03 Region III (Central Luzon)
6 04 Region IV-A (CALABARZON)
7 17 Region IV-B (MIMAROPA)
8 05 Region V (Bicol Region)
9 06 Region VI (Western Visayas)
10 07 Region VII (Central Visayas)
11 08 Region VIII (Eastern Visayas)
12 09 Region IX (Zamboanga Peninsula)
13 10 Region X (Northern Mindanao)
14 11 Region XI (Davao Region)
15 12 Region XII (Soccsksargen)
16 16 Region XIII (Caraga)
17 15 ARMM (Autonomous Region in Muslim Mindanao)

Matching and Inserting the PSGG Codes

In order to simplify our dataframe operations later, we match the PSGG codes under the psgg_code DF with their corresponding region names in the regions GDF and then insert the matched PSGG codes as a new column in the latter. To do this we apply the below function on the regions GDF.

def get_psgg_code(orig_name):
    if orig_name.startswith('Metropolitan'):
        return '13'
    else:
        choice, _ = process.extractOne(orig_name, psgg_code.region.values)
        p_code = psgg_code.psgg_code[psgg_code.region == choice]
        return p_code.values[0]

The above function uses the process.extractOne() method of the fuzzywuzzy package to match the most identical row from the psgg_code DF and use that value to return the corresponding PSGG code. We then apply said function on the REGION column of the regions GDF and assign the result to the same column.

regions['psgg_code'] = regions['REGION'].apply(lambda x: get_psgg_code(x))
regions

This should give us the following:

REGION geometry psgg_code
0 Autonomous Region of Muslim Mindanao (ARMM) (POLYGON ((119.4669418334962 4.586939811706523… 15
1 Bicol Region (Region V) (POLYGON ((122.9841690063476 11.71055984497071… 05
2 CALABARZON (Region IV-A) (POLYGON ((125.2216567993167 10.43443965911868… 04
3 Cagayan Valley (Region II) (POLYGON ((122.4703979492188 16.91995048522949… 02
4 Caraga (Region XIII) (POLYGON ((126.417503356934 7.964169979095546,… 16
5 Central Luzon (Region III) (POLYGON ((120.6236267089844 14.36787986755386… 03
6 Central Visayas (Region VII) (POLYGON ((123.2711105346679 9.084759712219269… 07
7 Cordillera Administrative Region (CAR) POLYGON ((121.3767929077149 17.95472908020015,… 14
8 Davao Region (Region XI) (POLYGON ((125.3977813720705 5.435830116271932… 11
9 Eastern Visayas (Region VIII) (POLYGON ((125.0736083984376 9.89472007751475,… 08
10 Ilocos Region (Region I) (POLYGON ((119.8650512695319 15.81169986724875… 01
11 MIMAROPA (Region IV-B) (POLYGON ((117.3138885498046 7.514170169830424… 17
12 Metropolitan Manila (POLYGON ((120.9797210693363 14.49305534362795… 13
13 Northern Mindanao (Region X) (POLYGON ((123.6219329833985 7.828589916229499… 10
14 SOCCSKSARGEN (Region XII) POLYGON ((124.5379867553711 7.681869983673105,… 12
15 Western Visayas (Region VI) (POLYGON ((122.4352188110355 9.643819808960162… 06
16 Zamboanga Peninsula (Region IX) (POLYGON ((122.0622329711914 6.872779846191344… 09

Looks like everything’s in order. Let’s set the pscc_code as the indexx for the regions GDF to clear things up a bit and make indexing simpler later.

regions.set_index('psgg_code', inplace=True)
regions

psgg_code REGION geometry
15 Autonomous Region of Muslim Mindanao (ARMM) (POLYGON ((119.4669418334962 4.586939811706523…
05 Bicol Region (Region V) (POLYGON ((122.9841690063476 11.71055984497071…
04 CALABARZON (Region IV-A) (POLYGON ((125.2216567993167 10.43443965911868…
02 Cagayan Valley (Region II) (POLYGON ((122.4703979492188 16.91995048522949…
16 Caraga (Region XIII) (POLYGON ((126.417503356934 7.964169979095546,…
03 Central Luzon (Region III) (POLYGON ((120.6236267089844 14.36787986755386…
07 Central Visayas (Region VII) (POLYGON ((123.2711105346679 9.084759712219269…
14 Cordillera Administrative Region (CAR) POLYGON ((121.3767929077149 17.95472908020015,…
11 Davao Region (Region XI) (POLYGON ((125.3977813720705 5.435830116271932…
08 Eastern Visayas (Region VIII) (POLYGON ((125.0736083984376 9.89472007751475,…
01 Ilocos Region (Region I) (POLYGON ((119.8650512695319 15.81169986724875…
17 MIMAROPA (Region IV-B) (POLYGON ((117.3138885498046 7.514170169830424…
13 Metropolitan Manila (POLYGON ((120.9797210693363 14.49305534362795…
10 Northern Mindanao (Region X) (POLYGON ((123.6219329833985 7.828589916229499…
12 SOCCSKSARGEN (Region XII) POLYGON ((124.5379867553711 7.681869983673105,…
06 Western Visayas (Region VI) (POLYGON ((122.4352188110355 9.643819808960162…
09 Zamboanga Peninsula (Region IX) (POLYGON ((122.0622329711914 6.872779846191344…

Plotting the Old Regional Shape File

Let’s take a “before” snapshot of the previous (pre-2015) regional boundaries, so that we’ll have something to remind us of the way things used to be.

# create a series with the region numbers/abbreviations to indicate map locations
def make_map_text(name):
    text = name.split(' ')
    return text[1] if name.startswith('Region') else text[0]

All the preceding function does is extract the Roman numerals from the region names (or the acronym, in the case of ARMM, CAR, etc.) since we’ll be using these to mark locations on the map later on. Applying this, we get:

map_names = psgg_code.loc[:, ['psgg_code', 'region']]
map_names['region'] = map_names['region'].apply(lambda x: make_map_text(x))
map_names.set_index('psgg_code', inplace=True, drop=True)

We now do the actual plotting with the below code block:

# Plotting it
plt.style.use('ggplot')
fig, ax = plt.subplots(figsize=(10, 10))

regions.plot(ax=ax, color='forestgreen', linewidth=0)
regions[regions.index == '06'].plot(ax=ax, color='orange', linewidth=0)
regions[regions.index == '07'].plot(ax=ax, color='slateblue', linewidth=0)

for i, point in regions.centroid.iteritems():
    reg_n = map_names.loc[i, 'region']
    ax.text(s=reg_n, x=point.x, y=point.y, fontsize='large')

ax.set_title('PH Administrative Regions (pre-2015)')
ax.set_axis_off()

plt.savefig('map-01-previous.png', bbox_inches='tight')

Geopandas allows us to use pandas’s .plot() function (which nicely wraps many of matplotlib‘s plotting routines) to generate the map directly from our regions GeoDataFrame or any slice thereof. It also lets us easily find the centroid of a given geometry object. We use the centroid to give us the coordinates for localizing the labels we’ve generated from our make_map_text() helper function. Here’s what the map looks like:

PH administrative regions, pre-2015. (Chart by DC Dabbler with data from PhilGIS.org
PH administrative regions, pre-2015. (Chart by DC Dabbler with data from PhilGIS.org

Here, we plot the old, pre-2015 shape file and highlight Regions VI and VII (colored orange and slate blue) from the rest of the country (colored forest green). It can be seen that the island of Negros is still divided into Regions VI and VII in this map.

Updating the Regional Boundaries

We’re just about ready to modify the previous geometry objects in order to accountfor the creation of the Negros Island Region (NIR) as a separate administrative unit.

The task at hand seems fairly straightforward. We first use the province-level geometry objects for Negros Occidental and Negros Oriental to ‘subtract’ these shapes from the geometry objects of Regions VI and VII, respectively. Then we take the union of the Negros Occidental and Oriental shapes as the geometry object for NIR.

To do the above steps more quickly and more accurately, we make use of shapely‘s methods for doing set theoretic operations on geometry objects. We can directly call these methods using any of the entries under the geometry fields in our regions and provinces since each of these entries is either a shapely polygon or multipolygon object already.

Enough talking, let’s get coding.

# get the shapes of the two provinces (see input cell 8 and its output)
neg_oc = neg_prov.iloc[0, 1]                # Negros Occidental
neg_or = neg_prov.iloc[1, 1]                # Negros Oriental

# get shapes of regions vi and vii
reg_6 = regions.loc['06', 'geometry']
reg_7 = regions.loc['07', 'geometry']

# remove the provinces from their respective pre-2015 regions
regions.loc['06', 'geometry'] = reg_6.difference(neg_oc)
regions.loc['07', 'geometry'] = reg_7.difference(neg_or)

In the preceding block of code, we first isolate the geometry data for the two Negros provinces. We then subtract these from their respective pre-2015 regions by calling the .difference() method of the regions’ multi-polygon data, passing the associated Negros province as argument.

Now we take the union of the two geometry objects representing the two Negros provinces.

# combining the two provinces' shapes
neg_geom = neg_oc.union(neg_or)

# appending the new region to the regions GDF
neg_reg = gpd.GeoDataFrame([{'REGION': 'Negros Island Region (NIR)',
                             'geometry': neg_geom,
                             'psgg_code': '18'}],
                            columns=['REGION', 'geometry', 'psgg_code'])
neg_reg.set_index('psgg_code', inplace=True, drop=True)
regions = regions.append(neg_reg)
regions

In the above code block, after combining the two Negros provinces’ geometry data, we create a new GeoDataFrame object named neg_reg which consists of only one row (the NIR entry) and three columns (which we made sure to have exactly the same names as the regions columns). We then set neg_reg‘s psgg_code as its new index. We did all this so that, everything ends up where they should once we append the neg_reg GDF as a new row of the regions GDF.

To check, here’s what the regions GDF looks like after we appended the NIR data:

psgg_code REGION geometry
15 Autonomous Region of Muslim Mindanao (ARMM) (POLYGON ((119.4669418334962 4.586939811706523…
05 Bicol Region (Region V) (POLYGON ((122.9841690063476 11.71055984497071…
04 CALABARZON (Region IV-A) (POLYGON ((125.2216567993167 10.43443965911868…
02 Cagayan Valley (Region II) (POLYGON ((122.4703979492188 16.91995048522949…
16 Caraga (Region XIII) (POLYGON ((126.417503356934 7.964169979095546,…
03 Central Luzon (Region III) (POLYGON ((120.6236267089844 14.36787986755386…
07 Central Visayas (Region VII) (POLYGON ((123.6044769287113 9.300559997558809…
14 Cordillera Administrative Region (CAR) POLYGON ((121.3767929077149 17.95472908020015,…
11 Davao Region (Region XI) (POLYGON ((125.3977813720705 5.435830116271932…
08 Eastern Visayas (Region VIII) (POLYGON ((125.0736083984376 9.89472007751475,…
01 Ilocos Region (Region I) (POLYGON ((119.8650512695319 15.81169986724875…
17 MIMAROPA (Region IV-B) (POLYGON ((117.3138885498046 7.514170169830424…
13 Metropolitan Manila (POLYGON ((120.9797210693363 14.49305534362795…
10 Northern Mindanao (Region X) (POLYGON ((123.6219329833985 7.828589916229499…
12 SOCCSKSARGEN (Region XII) POLYGON ((124.5379867553711 7.681869983673105,…
06 Western Visayas (Region VI) (POLYGON ((122.5980300903325 10.32855033874531…
09 Zamboanga Peninsula (Region IX) (POLYGON ((122.0622329711914 6.872779846191344…
18 Negros Island Region (NIR) (POLYGON ((122.4352188110356 9.643819808959961…

This tells us that we’ve successfully added NIR into our regions dataset.

Plotting the New Shapes

Everything seems to be in order, so far. But the only way to find out for sure is to plot the map based on the modified regions GDF. We’ll do that here.

plt.style.use('ggplot')
fig, ax = plt.subplots(figsize=(10, 10))

regions.plot(ax=ax, cmap='Dark2', linewidth=0)

for i, point in regions.centroid.iteritems():
    reg_n = map_names.loc[i, 'region']
    ax.text(s=reg_n, x=point.x, y=point.y, fontsize='large')

ax.set_title('PH Administrative Regions (Present)')
ax.set_axis_off()
plt.savefig('map-02-present.png', bbox_inches='tight')

This produces the following map:

PH administrative regions, present. (Chart by DC Dabbler with data from PhilGIS.org and GADM Database)
PH administrative regions, present. (Chart by DC Dabbler with data from PhilGIS.org and GADM Database)

Now, we can see the island of Negros unified under a single administrative unit that’s separate from both Regions VI and VII.

Generating the New Shape File

Geopandas also makes it easy to generate .shp files and associated file types (.dbf, .shx, etc.). So, we’ll do that in this section.

But first, I’m going to make a little modification on the region name for NCR as it currently appears in our regions GDF. I’m going to replace it with NCR’s official title and include the abbreviation in parentheses for the sake of consistency (plus it’s going to make it much easier for anyone to apply regex or string operations on the region names in the future).

regions.loc['13', 'REGION'] = 'National Capital Region (NCR)'
regions

The above block of code gives the following result:

psgg_code REGION geometry
15 Autonomous Region of Muslim Mindanao (ARMM) (POLYGON ((119.4669418334962 4.586939811706523…
05 Bicol Region (Region V) (POLYGON ((122.9841690063476 11.71055984497071…
04 CALABARZON (Region IV-A) (POLYGON ((125.2216567993167 10.43443965911868…
02 Cagayan Valley (Region II) (POLYGON ((122.4703979492188 16.91995048522949…
16 Caraga (Region XIII) (POLYGON ((126.417503356934 7.964169979095546,…
03 Central Luzon (Region III) (POLYGON ((120.6236267089844 14.36787986755386…
07 Central Visayas (Region VII) (POLYGON ((123.6044769287113 9.300559997558809…
14 Cordillera Administrative Region (CAR) POLYGON ((121.3767929077149 17.95472908020015,…
11 Davao Region (Region XI) (POLYGON ((125.3977813720705 5.435830116271932…
08 Eastern Visayas (Region VIII) (POLYGON ((125.0736083984376 9.89472007751475,…
01 Ilocos Region (Region I) (POLYGON ((119.8650512695319 15.81169986724875…
17 MIMAROPA (Region IV-B) (POLYGON ((117.3138885498046 7.514170169830424…
13 National Capital Region (NCR) (POLYGON ((120.9797210693363 14.49305534362795…
10 Northern Mindanao (Region X) (POLYGON ((123.6219329833985 7.828589916229499…
12 SOCCSKSARGEN (Region XII) POLYGON ((124.5379867553711 7.681869983673105,…
06 Western Visayas (Region VI) (POLYGON ((122.5980300903325 10.32855033874531…
09 Zamboanga Peninsula (Region IX) (POLYGON ((122.0622329711914 6.872779846191344…
18 Negros Island Region (NIR) (POLYGON ((122.4352188110356 9.643819808959961…

Looks good. Now, let’s build ourselves some shape file. The following line of code should generate everything we want/need in the Updated directory of this project.

regions.to_file('Updated/ph-regions-2015.shp', driver='ESRI Shapefile')

The Takeaway

Now, we have an up-to-date set of shape files for Philippine regions we can use to visualize region-level data such as the gross regional domestic product (GRDP), regional CPI, regional unemployment rates, or number of stand-up comedians by region.

Just one last thing, I’m making the output data files of this notebook freely available for anyone to download and use under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license, but this only applies to the output files and not the original shape files (which may be governed by another license). Please see this project’s README file for more on this.
Update: Please see the Updated directory of this notebook’s repository to view/access the output, or click here to download the zip file containing the updated GIS data.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.