Working with categorical data: land cover

This example builds upon the sample data for São Miguel introduced here to demonstrate the use of exactextract with categorical land cover data.

The following plot shows a sample of the CORINE 2018 landcover dataset that is distributed with exactextractr.

[1]:
import geopandas as gpd
import rasterio
import rasterio.plot
from matplotlib import pyplot

landcov = 'sao_miguel/clc2018_v2020_20u1.tif'
concelhos = 'sao_miguel/concelhos.gpkg'

fig, ax = pyplot.subplots()

with rasterio.open(landcov) as r:
    rasterio.plot.show(r, ax=ax, cmap='tab20c')

admin = gpd.read_file(concelhos)
admin.plot(ax=ax, facecolor='none', edgecolor='black')
[1]:
<Axes: >
_images/landcov_1_1.png

Summarizing land cover classifications

One of the most basic questions we might ask is which land cover type is predominant in each concelho. We can do this with the built-in mode summary operation. The minority and variety operations are also applicable to categorical data and provide the least-common classification and number of distinct classifications, respectively.

[2]:
from exactextract import exact_extract

df = exact_extract(landcov, concelhos, ["variety", "mode", "minority"], include_cols='name', output='pandas')
display(df)

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.3.4 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.11/dist-packages/ipykernel_launcher.py", line 18, in <module>
    app.launch_new_instance()
  File "/usr/local/lib/python3.11/dist-packages/traitlets/config/application.py", line 1075, in launch_instance
    app.start()
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelapp.py", line 758, in start
    self.io_loop.start()
  File "/usr/local/lib/python3.11/dist-packages/tornado/platform/asyncio.py", line 211, in start
    self.asyncio_loop.run_forever()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
    handle._run()
  File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 614, in shell_main
    await self.dispatch_shell(msg, subshell_id=subshell_id)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 471, in dispatch_shell
    await result
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 366, in execute_request
    await super().execute_request(stream, ident, parent)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 827, in execute_request
    reply_content = await reply_content
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 458, in do_execute
    res = shell.run_cell(
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/zmqshell.py", line 663, in run_cell
    return super().run_cell(*args, **kwargs)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3116, in run_cell
    result = self._run_cell(
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3171, in _run_cell
    result = runner(coro)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
    coro.send(None)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3394, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3639, in run_ast_nodes
    if await self.run_code(code, result, async_=asy):
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3699, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_959/2773434591.py", line 3, in <module>
    df = exact_extract(landcov, concelhos, ["variety", "mode", "minority"], include_cols='name', output='pandas')
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 431, in exact_extract
    rast = prep_raster(rast)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 140, in prep_raster
    sources = loader(rast, name_root)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 44, in prep_raster_gdal
    from osgeo import gdal, gdal_array  # noqa: F401
  File "/usr/lib/python3/dist-packages/osgeo/gdal_array.py", line 13, in <module>
    from . import _gdal_array
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: _ARRAY_API not found
name variety mode minority
0 Lagoa 11 21 9
1 Nordeste 12 24 44
2 Ponta Delgada 21 18 5
3 Povoação 15 23 26
4 Ribeira Grande 16 21 44
5 Vila Franca do Campo 12 21 29

If needed, we can load the attribute table distributed with the landcover dataset and replace the numeric codes above with their descriptions.

[3]:
import geopandas as pd

classes = gpd.read_file('sao_miguel/clc2018_v2020_20u1.tif.vat.dbf').set_index('Value')
for col in ('mode', 'minority'):
    df[col] = df[col].map(classes['LABEL3'])
display(df)
name variety mode minority
0 Lagoa 11 Land principally occupied by agriculture, with... Construction sites
1 Nordeste 12 Coniferous forest Sea and ocean
2 Ponta Delgada 21 Pastures Port areas
3 Povoação 15 Broad-leaved forest Natural grasslands
4 Ribeira Grande 16 Land principally occupied by agriculture, with... Sea and ocean
5 Vila Franca do Campo 12 Land principally occupied by agriculture, with... Transitional woodland-shrub

Calculating the fraction of each land cover type

If we want more detailed information, the unique operation provides an array of distinct landcover types within each polygon. The frac operation provides a matching array with fraction of the polygon’s Cartesian area that is covered by each type.

[4]:
df = exact_extract(landcov, concelhos, ["unique", "frac"], include_cols='name', output='pandas')
display(df)

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.3.4 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.11/dist-packages/ipykernel_launcher.py", line 18, in <module>
    app.launch_new_instance()
  File "/usr/local/lib/python3.11/dist-packages/traitlets/config/application.py", line 1075, in launch_instance
    app.start()
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelapp.py", line 758, in start
    self.io_loop.start()
  File "/usr/local/lib/python3.11/dist-packages/tornado/platform/asyncio.py", line 211, in start
    self.asyncio_loop.run_forever()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
    handle._run()
  File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 614, in shell_main
    await self.dispatch_shell(msg, subshell_id=subshell_id)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 471, in dispatch_shell
    await result
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 366, in execute_request
    await super().execute_request(stream, ident, parent)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 827, in execute_request
    reply_content = await reply_content
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 458, in do_execute
    res = shell.run_cell(
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/zmqshell.py", line 663, in run_cell
    return super().run_cell(*args, **kwargs)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3116, in run_cell
    result = self._run_cell(
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3171, in _run_cell
    result = runner(coro)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
    coro.send(None)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3394, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3639, in run_ast_nodes
    if await self.run_code(code, result, async_=asy):
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3699, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_959/2390232542.py", line 1, in <module>
    df = exact_extract(landcov, concelhos, ["unique", "frac"], include_cols='name', output='pandas')
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 431, in exact_extract
    rast = prep_raster(rast)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 140, in prep_raster
    sources = loader(rast, name_root)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 44, in prep_raster_gdal
    from osgeo import gdal, gdal_array  # noqa: F401
  File "/usr/lib/python3/dist-packages/osgeo/gdal_array.py", line 13, in <module>
    from . import _gdal_array
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: _ARRAY_API not found
name unique frac
0 Lagoa [9, 2, 27, 21, 12, 7, 20, 44, 18, 23, 3] [0.0063711393558363635, 0.06296995715158163, 0...
1 Nordeste [26, 29, 24, 21, 2, 20, 23, 25, 12, 27, 18, 44] [0.011951440831186277, 0.03741980964021535, 0....
2 Ponta Delgada [5, 1, 10, 6, 8, 7, 3, 11, 27, 25, 44, 18, 20,... [0.0006163751291919294, 0.0011556906409785125,...
3 Povoação [25, 27, 26, 29, 24, 11, 21, 18, 44, 23, 10, 2... [0.015132325263604073, 0.08214924261333095, 7....
4 Ribeira Grande [3, 29, 44, 18, 41, 12, 25, 27, 2, 20, 7, 21, ... [0.01728772697537766, 0.015469030136234001, 0....
5 Vila Franca do Campo [20, 12, 25, 23, 2, 41, 29, 44, 18, 21, 27, 24] [0.12116542049248848, 0.049864986045216775, 0....

To join these codes with the description, we can unnest the data using the pandas explode function and then remap the fields as done previously.

[5]:
df = df.explode(['unique', 'frac'])
df['unique'] = df['unique'].map(classes['LABEL3'])
display(df)
name unique frac
0 Lagoa Construction sites 0.006371
0 Lagoa Discontinuous urban fabric 0.06297
0 Lagoa Moors and heathland 0.066856
0 Lagoa Land principally occupied by agriculture, with... 0.268145
0 Lagoa Non-irrigated arable land 0.169704
... ... ... ...
5 Vila Franca do Campo Sea and ocean 0.01868
5 Vila Franca do Campo Pastures 0.154672
5 Vila Franca do Campo Land principally occupied by agriculture, with... 0.359993
5 Vila Franca do Campo Moors and heathland 0.149568
5 Vila Franca do Campo Coniferous forest 0.020959

87 rows × 3 columns

Condensed output with frac_as_map

If we are working with JSON output, it is also possible to view the same information in a map format:

[6]:
exact_extract(landcov, concelhos, 'frac', include_cols='name', output='geojson', output_options={'frac_as_map': True})[:2]

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.3.4 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.11/dist-packages/ipykernel_launcher.py", line 18, in <module>
    app.launch_new_instance()
  File "/usr/local/lib/python3.11/dist-packages/traitlets/config/application.py", line 1075, in launch_instance
    app.start()
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelapp.py", line 758, in start
    self.io_loop.start()
  File "/usr/local/lib/python3.11/dist-packages/tornado/platform/asyncio.py", line 211, in start
    self.asyncio_loop.run_forever()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
    handle._run()
  File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 614, in shell_main
    await self.dispatch_shell(msg, subshell_id=subshell_id)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 471, in dispatch_shell
    await result
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 366, in execute_request
    await super().execute_request(stream, ident, parent)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 827, in execute_request
    reply_content = await reply_content
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 458, in do_execute
    res = shell.run_cell(
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/zmqshell.py", line 663, in run_cell
    return super().run_cell(*args, **kwargs)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3116, in run_cell
    result = self._run_cell(
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3171, in _run_cell
    result = runner(coro)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
    coro.send(None)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3394, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3639, in run_ast_nodes
    if await self.run_code(code, result, async_=asy):
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3699, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_959/3058920553.py", line 1, in <module>
    exact_extract(landcov, concelhos, 'frac', include_cols='name', output='geojson', output_options={'frac_as_map': True})[:2]
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 431, in exact_extract
    rast = prep_raster(rast)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 140, in prep_raster
    sources = loader(rast, name_root)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 44, in prep_raster_gdal
    from osgeo import gdal, gdal_array  # noqa: F401
  File "/usr/lib/python3/dist-packages/osgeo/gdal_array.py", line 13, in <module>
    from . import _gdal_array
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: _ARRAY_API not found
[6]:
[{'type': 'Feature',
  'properties': {'frac': {np.int32(9): np.float64(0.0063711393558363635),
    np.int32(2): np.float64(0.06296995715158163),
    np.int32(27): np.float64(0.06685635659192689),
    np.int32(21): np.float64(0.2681454914912148),
    np.int32(12): np.float64(0.16970419227699762),
    np.int32(7): np.float64(0.006594688105163955),
    np.int32(20): np.float64(0.15615313755220891),
    np.int32(44): np.float64(0.03141814277570226),
    np.int32(18): np.float64(0.1543907769765085),
    np.int32(23): np.float64(0.0638116264431592),
    np.int32(3): np.float64(0.013584491279699783)},
   'name': 'Lagoa'}},
 {'type': 'Feature',
  'properties': {'frac': {np.int32(26): np.float64(0.011951440831186277),
    np.int32(29): np.float64(0.03741980964021535),
    np.int32(24): np.float64(0.24256525747655305),
    np.int32(21): np.float64(0.14168078769862463),
    np.int32(2): np.float64(0.011527241099523834),
    np.int32(20): np.float64(0.07714274446896201),
    np.int32(23): np.float64(0.08590323108419894),
    np.int32(25): np.float64(0.008761123929946161),
    np.int32(12): np.float64(0.02967795033103945),
    np.int32(27): np.float64(0.1277189861975002),
    np.int32(18): np.float64(0.2246235200631785),
    np.int32(44): np.float64(0.0010279071790714376)},
   'name': 'Nordeste'}}]

Summarizing population land cover

One extension of the analysis above is to see which land covers are associated with human population in a given concelho. Is the population primary urban or rural?

As described in the basic usage example, the population density raster provides the most robust results in the presence of partially-covered pixels.

We are able to perform this analysis because the CORINE sample distributed with exactextractr has been reprojected from its native Lambert Equal Area projection into geographic coordinates consistent with GPW. Otherwise, working with multiple rasters in different projections requires transformation to a common grid using tools such as GDAL.

Very little about the call to exact_extract requires changing to incorporate population. We swap weighted_frac for frac and set weights = pop_density:

[7]:
pop_density = 'sao_miguel/gpw_v411_2020_density_2020.tif'

df = exact_extract(landcov, concelhos, ["unique", "weighted_frac"], weights=pop_density, include_cols='name', output='pandas')
df = df.explode(['unique', 'weighted_frac']).astype({'weighted_frac':'float64'})
df['unique'] = df['unique'].map(classes['LABEL3'])
df.sort_values('weighted_frac', ascending=False).drop_duplicates('name')

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.3.4 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.11/dist-packages/ipykernel_launcher.py", line 18, in <module>
    app.launch_new_instance()
  File "/usr/local/lib/python3.11/dist-packages/traitlets/config/application.py", line 1075, in launch_instance
    app.start()
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelapp.py", line 758, in start
    self.io_loop.start()
  File "/usr/local/lib/python3.11/dist-packages/tornado/platform/asyncio.py", line 211, in start
    self.asyncio_loop.run_forever()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
    handle._run()
  File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 614, in shell_main
    await self.dispatch_shell(msg, subshell_id=subshell_id)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 471, in dispatch_shell
    await result
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 366, in execute_request
    await super().execute_request(stream, ident, parent)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 827, in execute_request
    reply_content = await reply_content
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 458, in do_execute
    res = shell.run_cell(
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/zmqshell.py", line 663, in run_cell
    return super().run_cell(*args, **kwargs)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3116, in run_cell
    result = self._run_cell(
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3171, in _run_cell
    result = runner(coro)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
    coro.send(None)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3394, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3639, in run_ast_nodes
    if await self.run_code(code, result, async_=asy):
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3699, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_959/1965688547.py", line 3, in <module>
    df = exact_extract(landcov, concelhos, ["unique", "weighted_frac"], weights=pop_density, include_cols='name', output='pandas')
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 431, in exact_extract
    rast = prep_raster(rast)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 140, in prep_raster
    sources = loader(rast, name_root)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 44, in prep_raster_gdal
    from osgeo import gdal, gdal_array  # noqa: F401
  File "/usr/lib/python3/dist-packages/osgeo/gdal_array.py", line 13, in <module>
    from . import _gdal_array
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: _ARRAY_API not found

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.3.4 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.11/dist-packages/ipykernel_launcher.py", line 18, in <module>
    app.launch_new_instance()
  File "/usr/local/lib/python3.11/dist-packages/traitlets/config/application.py", line 1075, in launch_instance
    app.start()
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelapp.py", line 758, in start
    self.io_loop.start()
  File "/usr/local/lib/python3.11/dist-packages/tornado/platform/asyncio.py", line 211, in start
    self.asyncio_loop.run_forever()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
    handle._run()
  File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 614, in shell_main
    await self.dispatch_shell(msg, subshell_id=subshell_id)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 471, in dispatch_shell
    await result
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 366, in execute_request
    await super().execute_request(stream, ident, parent)
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/kernelbase.py", line 827, in execute_request
    reply_content = await reply_content
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/ipkernel.py", line 458, in do_execute
    res = shell.run_cell(
  File "/usr/local/lib/python3.11/dist-packages/ipykernel/zmqshell.py", line 663, in run_cell
    return super().run_cell(*args, **kwargs)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3116, in run_cell
    result = self._run_cell(
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3171, in _run_cell
    result = runner(coro)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
    coro.send(None)
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3394, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3639, in run_ast_nodes
    if await self.run_code(code, result, async_=asy):
  File "/usr/local/lib/python3.11/dist-packages/IPython/core/interactiveshell.py", line 3699, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_959/1965688547.py", line 3, in <module>
    df = exact_extract(landcov, concelhos, ["unique", "weighted_frac"], weights=pop_density, include_cols='name', output='pandas')
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 432, in exact_extract
    weights = prep_raster(weights, name_root="weight")
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 140, in prep_raster
    sources = loader(rast, name_root)
  File "/usr/local/lib/python3.11/dist-packages/exactextract/exact_extract.py", line 44, in prep_raster_gdal
    from osgeo import gdal, gdal_array  # noqa: F401
  File "/usr/lib/python3/dist-packages/osgeo/gdal_array.py", line 13, in <module>
    from . import _gdal_array
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: _ARRAY_API not found
[7]:
name unique weighted_frac
0 Lagoa Discontinuous urban fabric 0.414716
2 Ponta Delgada Discontinuous urban fabric 0.381365
5 Vila Franca do Campo Discontinuous urban fabric 0.377407
4 Ribeira Grande Discontinuous urban fabric 0.373551
1 Nordeste Complex cultivation patterns 0.282158
3 Povoação Complex cultivation patterns 0.261401

Looking at the highest-population land cover type in each concelho, we can can see that the western/central concelhos of Lagoa, Ponta Delgada, Ribeira Grande, and Vila Franca do Campo have a more urban population than Nordeste or Povoação to the east.