PERF: Improve iterfeatures performance (#864)

Co-authored-by: Joris Van den Bossche <jorisvandenbossche@gmail.com>
This commit is contained in:
YuichiNotoya
2018-12-09 09:13:20 +01:00
committed by Joris Van den Bossche
co-authored by Joris Van den Bossche
parent 6e9b82b855
commit 4f933d38f1
2 changed files with 78 additions and 33 deletions
+38 -33
View File
@@ -327,45 +327,50 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
* keep: output the missing entries as NaN
show_bbox : include bbox (bounds) in the geojson. default False
"""
def fill_none(row):
"""
Takes in a Series, converts to a dictionary with null values
set to None
"""
na_keys = row.index[row.isnull()]
d = row.to_dict()
for k in na_keys:
d[k] = None
return d
# na_methods must take in a Series and return dict
na_methods = {'null': fill_none,
'drop': lambda row: row.dropna().to_dict(),
'keep': lambda row: row.to_dict()}
if na not in na_methods:
if na not in ['null', 'drop', 'keep']:
raise ValueError('Unknown na method {0}'.format(na))
f = na_methods[na]
for name, row in self.iterrows():
properties = f(row)
del properties[self._geometry_column_name]
ids = np.array(self.index, copy=False)
geometries = np.array(self[self._geometry_column_name], copy=False)
feature = {
'id': str(name),
'type': 'Feature',
'properties': properties,
'geometry': mapping(row[self._geometry_column_name])
if row[self._geometry_column_name] else None
}
properties_cols = self.columns.difference([self._geometry_column_name])
if show_bbox:
feature['bbox'] = row.geometry.bounds
if len(properties_cols) > 0:
# convert to object to get python scalars.
properties = self[properties_cols].astype(object).values
if na == 'null':
properties[pd.isnull(self[properties_cols]).values] = None
yield feature
for i, row in enumerate(properties):
geom = geometries[i]
if na == 'drop':
properties_items = dict((k, v) for k, v
in zip(properties_cols, row)
if not pd.isnull(v))
else:
properties_items = dict((k, v) for k, v
in zip(properties_cols, row))
feature = {'id': str(ids[i]),
'type': 'Feature',
'properties': properties_items,
'geometry': mapping(geom) if geom else None}
if show_bbox:
feature['bbox'] = geom.bounds if geom else None
yield feature
else:
for fid, geom in zip(ids, geometries):
feature = {'id': str(fid),
'type': 'Feature',
'properties': {},
'geometry': mapping(geom) if geom else None}
if show_bbox:
feature['bbox'] = geom.bounds if geom else None
yield feature
def _to_geo(self, **kwargs):
"""
+40
View File
@@ -619,6 +619,46 @@ class TestDataFrame:
assert self.df.__geo_interface__['type'] == 'FeatureCollection'
assert len(self.df.__geo_interface__['features']) == self.df.shape[0]
def test_geodataframe_iterfeatures(self):
df = self.df.iloc[:1].copy()
df.loc[0, 'BoroName'] = np.nan
# when containing missing values
# null: ouput the missing entries as JSON null
result = list(df.iterfeatures(na='null'))[0]['properties']
assert result['BoroName'] is None
# drop: remove the property from the feature.
result = list(df.iterfeatures(na='drop'))[0]['properties']
assert 'BoroName' not in result.keys()
# keep: output the missing entries as NaN
result = list(df.iterfeatures(na='keep'))[0]['properties']
assert np.isnan(result['BoroName'])
# test for checking that the (non-null) features are python scalars and
# not numpy scalars
assert type(df.loc[0, 'Shape_Leng']) is np.float64
# null
result = list(df.iterfeatures(na='null'))[0]
assert type(result['properties']['Shape_Leng']) is float
# drop
result = list(df.iterfeatures(na='drop'))[0]
assert type(result['properties']['Shape_Leng']) is float
# keep
result = list(df.iterfeatures(na='keep'))[0]
assert type(result['properties']['Shape_Leng']) is float
# when only having numerical columns
df_only_numerical_cols = df[['Shape_Leng', 'Shape_Area', 'geometry']]
assert type(df_only_numerical_cols.loc[0, 'Shape_Leng']) is np.float64
# null
result = list(df_only_numerical_cols.iterfeatures(na='null'))[0]
assert type(result['properties']['Shape_Leng']) is float
# drop
result = list(df_only_numerical_cols.iterfeatures(na='drop'))[0]
assert type(result['properties']['Shape_Leng']) is float
# keep
result = list(df_only_numerical_cols.iterfeatures(na='keep'))[0]
assert type(result['properties']['Shape_Leng']) is float
def test_geodataframe_geojson_no_bbox(self):
geo = self.df._to_geo(na="null", show_bbox=False)
assert 'bbox' not in geo.keys()