├── README.pdf
├── images
├── pivot.png
├── change_kernel.png
├── exec_location.png
└── exec_location2.png
├── environment.yml
├── tests
├── __init__.py
└── test_dataframe.py
├── LICENSE
├── .gitignore
├── Test Notebook.ipynb
├── pandas_cub
└── __init__.py
├── pandas_cub_final
└── __init__.py
├── README.md
└── data
└── employee.csv
/README.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tdpetrou/pandas_cub/HEAD/README.pdf
--------------------------------------------------------------------------------
/images/pivot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tdpetrou/pandas_cub/HEAD/images/pivot.png
--------------------------------------------------------------------------------
/images/change_kernel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tdpetrou/pandas_cub/HEAD/images/change_kernel.png
--------------------------------------------------------------------------------
/images/exec_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tdpetrou/pandas_cub/HEAD/images/exec_location.png
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: pandas_cub
2 | dependencies:
3 | - python=3.6
4 | - pandas
5 | - jupyter
6 | - pytest
--------------------------------------------------------------------------------
/images/exec_location2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tdpetrou/pandas_cub/HEAD/images/exec_location2.png
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from numpy.testing import assert_array_equal, assert_allclose
2 |
3 |
4 | def assert_df_equals(df1, df2):
5 | assert df1.columns == df2.columns
6 | for values1, values2 in zip(df1._data.values(), df2._data.values()):
7 | kind = values1.dtype.kind
8 | if kind == 'f':
9 | assert_allclose(values1, values2)
10 | else:
11 | assert_array_equal(values1, values2)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2018, Ted Petrou
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | .DS_Store
10 | .idea
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 | .pytest_cache/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 | db.sqlite3
60 |
61 | # Flask stuff:
62 | instance/
63 | .webassets-cache
64 |
65 | # Scrapy stuff:
66 | .scrapy
67 |
68 | # Sphinx documentation
69 | docs/_build/
70 |
71 | # PyBuilder
72 | target/
73 |
74 | # Jupyter Notebook
75 | .ipynb_checkpoints
76 |
77 | # pyenv
78 | .python-version
79 |
80 | # celery beat schedule file
81 | celerybeat-schedule
82 |
83 | # SageMath parsed files
84 | *.sage.py
85 |
86 | # Environments
87 | .env
88 | .venv
89 | env/
90 | venv/
91 | ENV/
92 | env.bak/
93 | venv.bak/
94 |
95 | # Spyder project settings
96 | .spyderproject
97 | .spyproject
98 |
99 | # Rope project settings
100 | .ropeproject
101 |
102 | # mkdocs documentation
103 | /site
104 |
105 | # mypy
106 | .mypy_cache/
107 |
--------------------------------------------------------------------------------
/Test Notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Test pandas_cub manually\n",
8 | "\n",
9 | "In this notebook, we will test pandas_cub manually."
10 | ]
11 | },
12 | {
13 | "cell_type": "code",
14 | "execution_count": null,
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import sys\n",
19 | "sys.executable"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "metadata": {},
26 | "outputs": [],
27 | "source": [
28 | "%load_ext autoreload\n",
29 | "%autoreload 2"
30 | ]
31 | },
32 | {
33 | "cell_type": "code",
34 | "execution_count": null,
35 | "metadata": {},
36 | "outputs": [],
37 | "source": [
38 | "import numpy as np\n",
39 | "import pandas_cub as pdc\n",
40 | "import pandas_cub_final as pdcf\n",
41 | "import pandas as pd"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": null,
47 | "metadata": {},
48 | "outputs": [],
49 | "source": [
50 | "name = np.array(['Penelope', 'Niko', 'Eleni'])\n",
51 | "state = np.array(['Texas', 'California', 'Texas'])\n",
52 | "height = np.array([3.6, 3.5, 5.2])\n",
53 | "school = np.array([True, False, True])\n",
54 | "weight = np.array([45, 40, 130])\n",
55 | "\n",
56 | "data = {'name': name, 'state': state, 'height': height, \n",
57 | " 'school': school, 'weight': weight}\n",
58 | "\n",
59 | "df = pdc.DataFrame(data)\n",
60 | "df_final = pdcf.DataFrame(data)\n",
61 | "df_pandas = pd.DataFrame(data)"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": null,
67 | "metadata": {},
68 | "outputs": [],
69 | "source": [
70 | "df"
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": null,
76 | "metadata": {},
77 | "outputs": [],
78 | "source": [
79 | "df_final"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "df_pandas"
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "metadata": {},
95 | "outputs": [],
96 | "source": []
97 | }
98 | ],
99 | "metadata": {
100 | "kernelspec": {
101 | "display_name": "Python 3",
102 | "language": "python",
103 | "name": "python3"
104 | },
105 | "language_info": {
106 | "codemirror_mode": {
107 | "name": "ipython",
108 | "version": 3
109 | },
110 | "file_extension": ".py",
111 | "mimetype": "text/x-python",
112 | "name": "python",
113 | "nbconvert_exporter": "python",
114 | "pygments_lexer": "ipython3",
115 | "version": "3.6.8"
116 | }
117 | },
118 | "nbformat": 4,
119 | "nbformat_minor": 2
120 | }
121 |
--------------------------------------------------------------------------------
/pandas_cub/__init__.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | __version__ = '0.0.1'
4 |
5 |
6 | class DataFrame:
7 |
8 | def __init__(self, data):
9 | """
10 | A DataFrame holds two dimensional heterogeneous data. Create it by
11 | passing a dictionary of NumPy arrays to the values parameter
12 |
13 | Parameters
14 | ----------
15 | data: dict
16 | A dictionary of strings mapped to NumPy arrays. The key will
17 | become the column name.
18 | """
19 | # check for correct input types
20 | self._check_input_types(data)
21 |
22 | # check for equal array lengths
23 | self._check_array_lengths(data)
24 |
25 | # convert unicode arrays to object
26 | self._data = self._convert_unicode_to_object(data)
27 |
28 | # Allow for special methods for strings
29 | self.str = StringMethods(self)
30 | self._add_docs()
31 |
32 | def _check_input_types(self, data):
33 | pass
34 |
35 | def _check_array_lengths(self, data):
36 | pass
37 |
38 | def _convert_unicode_to_object(self, data):
39 | new_data = {}
40 | return new_data
41 |
42 | def __len__(self):
43 | """
44 | Make the builtin len function work with our dataframe
45 |
46 | Returns
47 | -------
48 | int: the number of rows in the dataframe
49 | """
50 | pass
51 |
52 | @property
53 | def columns(self):
54 | """
55 | _data holds column names mapped to arrays
56 | take advantage of internal ordering of dictionaries to
57 | put columns in correct order in list. Only works in 3.6+
58 |
59 | Returns
60 | -------
61 | list of column names
62 | """
63 | pass
64 |
65 | @columns.setter
66 | def columns(self, columns):
67 | """
68 | Must supply a list of columns as strings the same length
69 | as the current DataFrame
70 |
71 | Parameters
72 | ----------
73 | columns: list of strings
74 |
75 | Returns
76 | -------
77 | None
78 | """
79 | pass
80 |
81 | @property
82 | def shape(self):
83 | """
84 | Returns
85 | -------
86 | two-item tuple of number of rows and columns
87 | """
88 | pass
89 |
90 | def _repr_html_(self):
91 | """
92 | Used to create a string of HTML to nicely display the DataFrame
93 | in a Jupyter Notebook. Different string formatting is used for
94 | different data types.
95 |
96 | The structure of the HTML is as follows:
97 |
98 |
99 |
100 | | data |
101 | ...
102 | data |
103 |
104 |
105 |
106 |
107 | | {i} |
108 | data |
109 | ...
110 | data |
111 |
112 | ...
113 |
114 | | {i} |
115 | data |
116 | ...
117 | data |
118 |
119 |
120 |
121 | """
122 | pass
123 |
124 | @property
125 | def values(self):
126 | """
127 | Returns
128 | -------
129 | A single 2D NumPy array of the underlying data
130 | """
131 | pass
132 |
133 | @property
134 | def dtypes(self):
135 | """
136 | Returns
137 | -------
138 | A two-column DataFrame of column names in one column and
139 | their data type in the other
140 | """
141 | DTYPE_NAME = {'O': 'string', 'i': 'int', 'f': 'float', 'b': 'bool'}
142 | pass
143 |
144 | def __getitem__(self, item):
145 | """
146 | Use the brackets operator to simultaneously select rows and columns
147 | A single string selects one column -> df['colname']
148 | A list of strings selects multiple columns -> df[['colname1', 'colname2']]
149 | A one column DataFrame of booleans that filters rows -> df[df_bool]
150 | Row and column selection simultaneously -> df[rs, cs]
151 | where cs and rs can be integers, slices, or a list of integers
152 | rs can also be a one-column boolean DataFrame
153 |
154 | Returns
155 | -------
156 | A subset of the original DataFrame
157 | """
158 | pass
159 |
160 | def _getitem_tuple(self, item):
161 | # simultaneous selection of rows and cols -> df[rs, cs]
162 | pass
163 |
164 | def _ipython_key_completions_(self):
165 | # allows for tab completion when doing df['c
166 | pass
167 |
168 | def __setitem__(self, key, value):
169 | # adds a new column or a overwrites an old column
170 | pass
171 |
172 | def head(self, n=5):
173 | """
174 | Return the first n rows
175 |
176 | Parameters
177 | ----------
178 | n: int
179 |
180 | Returns
181 | -------
182 | DataFrame
183 | """
184 | pass
185 |
186 | def tail(self, n=5):
187 | """
188 | Return the last n rows
189 |
190 | Parameters
191 | ----------
192 | n: int
193 |
194 | Returns
195 | -------
196 | DataFrame
197 | """
198 | pass
199 |
200 | #### Aggregation Methods ####
201 |
202 | def min(self):
203 | return self._agg(np.min)
204 |
205 | def max(self):
206 | return self._agg(np.max)
207 |
208 | def mean(self):
209 | return self._agg(np.mean)
210 |
211 | def median(self):
212 | return self._agg(np.median)
213 |
214 | def sum(self):
215 | return self._agg(np.sum)
216 |
217 | def var(self):
218 | return self._agg(np.var)
219 |
220 | def std(self):
221 | return self._agg(np.std)
222 |
223 | def all(self):
224 | return self._agg(np.all)
225 |
226 | def any(self):
227 | return self._agg(np.any)
228 |
229 | def argmax(self):
230 | return self._agg(np.argmax)
231 |
232 | def argmin(self):
233 | return self._agg(np.argmin)
234 |
235 | def _agg(self, aggfunc):
236 | """
237 | Generic aggregation function that applies the
238 | aggregation to each column
239 |
240 | Parameters
241 | ----------
242 | aggfunc: str of the aggregation function name in NumPy
243 |
244 | Returns
245 | -------
246 | A DataFrame
247 | """
248 | pass
249 |
250 | def isna(self):
251 | """
252 | Determines whether each value in the DataFrame is missing or not
253 |
254 | Returns
255 | -------
256 | A DataFrame of booleans the same size as the calling DataFrame
257 | """
258 | pass
259 |
260 | def count(self):
261 | """
262 | Counts the number of non-missing values per column
263 |
264 | Returns
265 | -------
266 | A DataFrame
267 | """
268 | pass
269 |
270 | def unique(self):
271 | """
272 | Finds the unique values of each column
273 |
274 | Returns
275 | -------
276 | A list of one-column DataFrames
277 | """
278 | pass
279 |
280 | def nunique(self):
281 | """
282 | Find the number of unique values in each column
283 |
284 | Returns
285 | -------
286 | A DataFrame
287 | """
288 | pass
289 |
290 | def value_counts(self, normalize=False):
291 | """
292 | Returns the frequency of each unique value for each column
293 |
294 | Parameters
295 | ----------
296 | normalize: bool
297 | If True, returns the relative frequencies (percent)
298 |
299 | Returns
300 | -------
301 | A list of DataFrames or a single DataFrame if one column
302 | """
303 | pass
304 |
305 | def rename(self, columns):
306 | """
307 | Renames columns in the DataFrame
308 |
309 | Parameters
310 | ----------
311 | columns: dict
312 | A dictionary mapping the old column name to the new column name
313 |
314 | Returns
315 | -------
316 | A DataFrame
317 | """
318 | pass
319 |
320 | def drop(self, columns):
321 | """
322 | Drops one or more columns from a DataFrame
323 |
324 | Parameters
325 | ----------
326 | columns: str or list of strings
327 |
328 | Returns
329 | -------
330 | A DataFrame
331 | """
332 | pass
333 |
334 | #### Non-Aggregation Methods ####
335 |
336 | def abs(self):
337 | """
338 | Takes the absolute value of each value in the DataFrame
339 |
340 | Returns
341 | -------
342 | A DataFrame
343 | """
344 | return self._non_agg(np.abs)
345 |
346 | def cummin(self):
347 | """
348 | Finds cumulative minimum by column
349 |
350 | Returns
351 | -------
352 | A DataFrame
353 | """
354 | return self._non_agg(np.minimum.accumulate)
355 |
356 | def cummax(self):
357 | """
358 | Finds cumulative maximum by column
359 |
360 | Returns
361 | -------
362 | A DataFrame
363 | """
364 | return self._non_agg(np.maximum.accumulate)
365 |
366 | def cumsum(self):
367 | """
368 | Finds cumulative sum by column
369 |
370 | Returns
371 | -------
372 | A DataFrame
373 | """
374 | return self._non_agg(np.cumsum)
375 |
376 | def clip(self, lower=None, upper=None):
377 | """
378 | All values less than lower will be set to lower
379 | All values greater than upper will be set to upper
380 |
381 | Parameters
382 | ----------
383 | lower: number or None
384 | upper: number or None
385 |
386 | Returns
387 | -------
388 | A DataFrame
389 | """
390 | return self._non_agg(np.clip, a_min=lower, a_max=upper)
391 |
392 | def round(self, n):
393 | """
394 | Rounds values to the nearest n decimals
395 |
396 | Returns
397 | -------
398 | A DataFrame
399 | """
400 | return self._non_agg(np.round, decimals=n)
401 |
402 | def copy(self):
403 | """
404 | Copies the DataFrame
405 |
406 | Returns
407 | -------
408 | A DataFrame
409 | """
410 | return self._non_agg(np.copy)
411 |
412 | def _non_agg(self, funcname, **kwargs):
413 | """
414 | Generic non-aggregation function
415 |
416 | Parameters
417 | ----------
418 | funcname: numpy function
419 | kwargs: extra keyword arguments for certain functions
420 |
421 | Returns
422 | -------
423 | A DataFrame
424 | """
425 | pass
426 |
427 | def diff(self, n=1):
428 | """
429 | Take the difference between the current value and
430 | the nth value above it.
431 |
432 | Parameters
433 | ----------
434 | n: int
435 |
436 | Returns
437 | -------
438 | A DataFrame
439 | """
440 | def func():
441 | pass
442 | return self._non_agg(func)
443 |
444 | def pct_change(self, n=1):
445 | """
446 | Take the percentage difference between the current value and
447 | the nth value above it.
448 |
449 | Parameters
450 | ----------
451 | n: int
452 |
453 | Returns
454 | -------
455 | A DataFrame
456 | """
457 | def func():
458 | pass
459 | return self._non_agg(func)
460 |
461 | #### Arithmetic and Comparison Operators ####
462 |
463 | def __add__(self, other):
464 | return self._oper('__add__', other)
465 |
466 | def __radd__(self, other):
467 | return self._oper('__radd__', other)
468 |
469 | def __sub__(self, other):
470 | return self._oper('__sub__', other)
471 |
472 | def __rsub__(self, other):
473 | return self._oper('__rsub__', other)
474 |
475 | def __mul__(self, other):
476 | return self._oper('__mul__', other)
477 |
478 | def __rmul__(self, other):
479 | return self._oper('__rmul__', other)
480 |
481 | def __truediv__(self, other):
482 | return self._oper('__truediv__', other)
483 |
484 | def __rtruediv__(self, other):
485 | return self._oper('__rtruediv__', other)
486 |
487 | def __floordiv__(self, other):
488 | return self._oper('__floordiv__', other)
489 |
490 | def __rfloordiv__(self, other):
491 | return self._oper('__rfloordiv__', other)
492 |
493 | def __pow__(self, other):
494 | return self._oper('__pow__', other)
495 |
496 | def __rpow__(self, other):
497 | return self._oper('__rpow__', other)
498 |
499 | def __gt__(self, other):
500 | return self._oper('__gt__', other)
501 |
502 | def __lt__(self, other):
503 | return self._oper('__lt__', other)
504 |
505 | def __ge__(self, other):
506 | return self._oper('__ge__', other)
507 |
508 | def __le__(self, other):
509 | return self._oper('__le__', other)
510 |
511 | def __ne__(self, other):
512 | return self._oper('__ne__', other)
513 |
514 | def __eq__(self, other):
515 | return self._oper('__eq__', other)
516 |
517 | def _oper(self, op, other):
518 | """
519 | Generic operator function
520 |
521 | Parameters
522 | ----------
523 | op: str name of special method
524 | other: the other object being operated on
525 |
526 | Returns
527 | -------
528 | A DataFrame
529 | """
530 | pass
531 |
532 | def sort_values(self, by, asc=True):
533 | """
534 | Sort the DataFrame by one or more values
535 |
536 | Parameters
537 | ----------
538 | by: str or list of column names
539 | asc: boolean of sorting order
540 |
541 | Returns
542 | -------
543 | A DataFrame
544 | """
545 | pass
546 |
547 | def sample(self, n=None, frac=None, replace=False, seed=None):
548 | """
549 | Randomly samples rows the DataFrame
550 |
551 | Parameters
552 | ----------
553 | n: int
554 | number of rows to return
555 | frac: float
556 | Proportion of the data to sample
557 | replace: bool
558 | Whether or not to sample with replacement
559 | seed: int
560 | Seeds the random number generator
561 |
562 | Returns
563 | -------
564 | A DataFrame
565 | """
566 | pass
567 |
568 | def pivot_table(self, rows=None, columns=None, values=None, aggfunc=None):
569 | """
570 | Creates a pivot table from one or two 'grouping' columns.
571 |
572 | Parameters
573 | ----------
574 | rows: str of column name to group by
575 | Optional
576 | columns: str of column name to group by
577 | Optional
578 | values: str of column name to aggregate
579 | Required
580 | aggfunc: str of aggregation function
581 |
582 | Returns
583 | -------
584 | A DataFrame
585 | """
586 | pass
587 |
588 | def _add_docs(self):
589 | agg_names = ['min', 'max', 'mean', 'median', 'sum', 'var',
590 | 'std', 'any', 'all', 'argmax', 'argmin']
591 | agg_doc = \
592 | """
593 | Find the {} of each column
594 |
595 | Returns
596 | -------
597 | DataFrame
598 | """
599 | for name in agg_names:
600 | getattr(DataFrame, name).__doc__ = agg_doc.format(name)
601 |
602 |
603 | class StringMethods:
604 |
605 | def __init__(self, df):
606 | self._df = df
607 |
608 | def capitalize(self, col):
609 | return self._str_method(str.capitalize, col)
610 |
611 | def center(self, col, width, fillchar=None):
612 | if fillchar is None:
613 | fillchar = ' '
614 | return self._str_method(str.center, col, width, fillchar)
615 |
616 | def count(self, col, sub, start=None, stop=None):
617 | return self._str_method(str.count, col, sub, start, stop)
618 |
619 | def endswith(self, col, suffix, start=None, stop=None):
620 | return self._str_method(str.endswith, col, suffix, start, stop)
621 |
622 | def startswith(self, col, suffix, start=None, stop=None):
623 | return self._str_method(str.startswith, col, suffix, start, stop)
624 |
625 | def find(self, col, sub, start=None, stop=None):
626 | return self._str_method(str.find, col, sub, start, stop)
627 |
628 | def len(self, col):
629 | return self._str_method(str.__len__, col)
630 |
631 | def get(self, col, item):
632 | return self._str_method(str.__getitem__, col, item)
633 |
634 | def index(self, col, sub, start=None, stop=None):
635 | return self._str_method(str.index, col, sub, start, stop)
636 |
637 | def isalnum(self, col):
638 | return self._str_method(str.isalnum, col)
639 |
640 | def isalpha(self, col):
641 | return self._str_method(str.isalpha, col)
642 |
643 | def isdecimal(self, col):
644 | return self._str_method(str.isdecimal, col)
645 |
646 | def islower(self, col):
647 | return self._str_method(str.islower, col)
648 |
649 | def isnumeric(self, col):
650 | return self._str_method(str.isnumeric, col)
651 |
652 | def isspace(self, col):
653 | return self._str_method(str.isspace, col)
654 |
655 | def istitle(self, col):
656 | return self._str_method(str.istitle, col)
657 |
658 | def isupper(self, col):
659 | return self._str_method(str.isupper, col)
660 |
661 | def lstrip(self, col, chars):
662 | return self._str_method(str.lstrip, col, chars)
663 |
664 | def rstrip(self, col, chars):
665 | return self._str_method(str.rstrip, col, chars)
666 |
667 | def strip(self, col, chars):
668 | return self._str_method(str.strip, col, chars)
669 |
670 | def replace(self, col, old, new, count=None):
671 | if count is None:
672 | count = -1
673 | return self._str_method(str.replace, col, old, new, count)
674 |
675 | def swapcase(self, col):
676 | return self._str_method(str.swapcase, col)
677 |
678 | def title(self, col):
679 | return self._str_method(str.title, col)
680 |
681 | def lower(self, col):
682 | return self._str_method(str.lower, col)
683 |
684 | def upper(self, col):
685 | return self._str_method(str.upper, col)
686 |
687 | def zfill(self, col, width):
688 | return self._str_method(str.zfill, col, width)
689 |
690 | def encode(self, col, encoding='utf-8', errors='strict'):
691 | return self._str_method(str.encode, col, encoding, errors)
692 |
693 | def _str_method(self, method, col, *args):
694 | pass
695 |
696 |
697 | def read_csv(fn):
698 | """
699 | Read in a comma-separated value file as a DataFrame
700 |
701 | Parameters
702 | ----------
703 | fn: string of file location
704 |
705 | Returns
706 | -------
707 | A DataFrame
708 | """
709 | pass
710 |
--------------------------------------------------------------------------------
/tests/test_dataframe.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from numpy.testing import assert_array_equal
3 | import pytest
4 |
5 | import pandas_cub as pdc
6 | from tests import assert_df_equals
7 |
8 | pytestmark = pytest.mark.filterwarnings("ignore")
9 |
10 | a = np.array(['a', 'b', 'c'])
11 | b = np.array(['c', 'd', None])
12 | c = np.random.rand(3)
13 | d = np.array([True, False, True])
14 | e = np.array([1, 2, 3])
15 | df = pdc.DataFrame({'a': a, 'b': b, 'c': c, 'd': d, 'e': e})
16 |
17 |
18 | class TestDataFrameCreation:
19 |
20 | def test_input_types(self):
21 | with pytest.raises(TypeError):
22 | pdc.DataFrame([1, 2, 3])
23 |
24 | with pytest.raises(TypeError):
25 | pdc.DataFrame({1: 5, 'b': 10})
26 |
27 | with pytest.raises(TypeError):
28 | pdc.DataFrame({'a': np.array([1]), 'b': 10})
29 |
30 | with pytest.raises(ValueError):
31 | pdc.DataFrame({'a': np.array([1]),
32 | 'b': np.array([[1]])})
33 |
34 | # correct construction. no error
35 | pdc.DataFrame({'a': np.array([1]),
36 | 'b': np.array([1])})
37 |
38 | def test_array_length(self):
39 | with pytest.raises(ValueError):
40 | pdc.DataFrame({'a': np.array([1, 2]),
41 | 'b': np.array([1])})
42 | # correct construction. no error
43 | pdc.DataFrame({'a': np.array([1, 2]),
44 | 'b': np.array([5, 10])})
45 |
46 | def test_unicode_to_object(self):
47 | a_object = a.astype('O')
48 | assert_array_equal(df._data['a'], a_object)
49 | assert_array_equal(df._data['b'], b)
50 | assert_array_equal(df._data['c'], c)
51 | assert_array_equal(df._data['d'], d)
52 | assert_array_equal(df._data['e'], e)
53 |
54 | def test_len(self):
55 | assert len(df) == 3
56 |
57 | def test_columns(self):
58 | assert df.columns == ['a', 'b', 'c', 'd', 'e']
59 |
60 | def test_set_columns(self):
61 | with pytest.raises(TypeError):
62 | df.columns = 5
63 |
64 | with pytest.raises(ValueError):
65 | df.columns = ['a', 'b']
66 |
67 | with pytest.raises(TypeError):
68 | df.columns = [1, 2, 3, 4, 5]
69 |
70 | with pytest.raises(ValueError):
71 | df.columns = ['f', 'f', 'g', 'h', 'i']
72 |
73 | df.columns = ['f', 'g', 'h', 'i', 'j']
74 | assert df.columns == ['f', 'g', 'h', 'i', 'j']
75 |
76 | # set it back
77 | df.columns = ['a', 'b', 'c', 'd', 'e']
78 | assert df.columns == ['a', 'b', 'c', 'd', 'e']
79 |
80 | def test_shape(self):
81 | assert df.shape == (3, 5)
82 |
83 | def test_values(self):
84 | values = np.column_stack((a, b, c, d, e))
85 | assert_array_equal(df.values, values)
86 |
87 | def test_dtypes(self):
88 | cols = np.array(['a', 'b', 'c', 'd', 'e'], dtype='O')
89 | dtypes = np.array(['string', 'string', 'float', 'bool', 'int'], dtype='O')
90 |
91 | df_result = df.dtypes
92 | df_answer = pdc.DataFrame({'Column Name': cols,
93 | 'Data Type': dtypes})
94 | assert_df_equals(df_result, df_answer)
95 |
96 |
97 | class TestSelection:
98 |
99 | def test_one_column(self):
100 | assert_array_equal(df['a'].values[:, 0], a)
101 | assert_array_equal(df['c'].values[:, 0], c)
102 |
103 | def test_multiple_columns(self):
104 | cols = ['a', 'c']
105 | df_result = df[cols]
106 | df_answer = pdc.DataFrame({'a': a, 'c': c})
107 | assert_df_equals(df_result, df_answer)
108 |
109 | def test_simple_boolean(self):
110 | bool_arr = np.array([True, False, False])
111 | df_bool = pdc.DataFrame({'col': bool_arr})
112 | df_result = df[df_bool]
113 | df_answer = pdc.DataFrame({'a': a[bool_arr], 'b': b[bool_arr],
114 | 'c': c[bool_arr], 'd': d[bool_arr],
115 | 'e': e[bool_arr]})
116 | assert_df_equals(df_result, df_answer)
117 |
118 | with pytest.raises(ValueError):
119 | df_bool = pdc.DataFrame({'col': bool_arr, 'col2': bool_arr})
120 | df[df_bool]
121 |
122 | with pytest.raises(TypeError):
123 | df_bool = pdc.DataFrame({'col': np.array[1, 2, 3]})
124 |
125 | def test_one_column_tuple(self):
126 | assert_df_equals(df[:, 'a'], pdc.DataFrame({'a': a}))
127 |
128 | def test_multiple_columns_tuple(self):
129 | cols = ['a', 'c']
130 | df_result = df[:, cols]
131 | df_answer = pdc.DataFrame({'a': a, 'c': c})
132 | assert_df_equals(df_result, df_answer)
133 |
134 | def test_int_selcetion(self):
135 | assert_df_equals(df[:, 3], pdc.DataFrame({'d': d}))
136 |
137 | def test_simultaneous_tuple(self):
138 | with pytest.raises(TypeError):
139 | s = set()
140 | df[s]
141 |
142 | with pytest.raises(ValueError):
143 | df[1, 2, 3]
144 |
145 | def test_single_element(self):
146 | df_answer = pdc.DataFrame({'e': np.array([2])})
147 | assert_df_equals(df[1, 'e'], df_answer)
148 |
149 | def test_all_row_selections(self):
150 | df1 = pdc.DataFrame({'a': np.array([True, False, True]),
151 | 'b': np.array([1, 3, 5])})
152 | with pytest.raises(ValueError):
153 | df[df1, 'e']
154 |
155 | with pytest.raises(TypeError):
156 | df[df1['b'], 'c']
157 |
158 | df_result = df[df1['a'], 'c']
159 | df_answer = pdc.DataFrame({'c': c[[True, False, True]]})
160 | assert_df_equals(df_result, df_answer)
161 |
162 | df_result = df[[1, 2], 0]
163 | df_answer = pdc.DataFrame({'a': a[[1, 2]]})
164 | assert_df_equals(df_result, df_answer)
165 |
166 | df_result = df[1:, 0]
167 | assert_df_equals(df_result, df_answer)
168 |
169 | def test_list_columns(self):
170 | df_answer = pdc.DataFrame({'c': c, 'e': e})
171 | assert_df_equals(df[:, [2, 4]], df_answer)
172 | assert_df_equals(df[:, [2, 'e']], df_answer)
173 | assert_df_equals(df[:, ['c', 'e']], df_answer)
174 |
175 | df_result = df[2, ['a', 'e']]
176 | df_answer = pdc.DataFrame({'a': a[[2]], 'e': e[[2]]})
177 | assert_df_equals(df_result, df_answer)
178 |
179 | df_answer = pdc.DataFrame({'c': c[[1, 2]], 'e': e[[1, 2]]})
180 | assert_df_equals(df[[1, 2], ['c', 'e']], df_answer)
181 |
182 | df1 = pdc.DataFrame({'a': np.array([True, False, True]),
183 | 'b': np.array([1, 3, 5])})
184 | df_answer = pdc.DataFrame({'c': c[[0, 2]], 'e': e[[0, 2]]})
185 | assert_df_equals(df[df1['a'], ['c', 'e']], df_answer)
186 |
187 | def test_col_slice(self):
188 | df_answer = pdc.DataFrame({'a': a, 'b': b, 'c': c})
189 | assert_df_equals(df[:, :3], df_answer)
190 |
191 | df_answer = pdc.DataFrame({'a': a[::2], 'b': b[::2], 'c': c[::2]})
192 | assert_df_equals(df[::2, :3], df_answer)
193 |
194 | df_answer = pdc.DataFrame({'a': a[::2], 'b': b[::2], 'c': c[::2], 'd': d[::2], 'e': e[::2]})
195 | assert_df_equals(df[::2, :], df_answer)
196 |
197 | with pytest.raises(TypeError):
198 | df[:, set()]
199 |
200 | def test_tab_complete(self):
201 | assert ['a', 'b', 'c', 'd', 'e'] == df._ipython_key_completions_()
202 |
203 | def test_new_column(self):
204 | df_result = pdc.DataFrame({'a': a, 'b': b, 'c': c, 'd': d, 'e': e})
205 | f = np.array([1.5, 23, 4.11])
206 | df_result['f'] = f
207 | df_answer = pdc.DataFrame({'a': a, 'b': b, 'c': c, 'd': d, 'e': e, 'f': f})
208 | assert_df_equals(df_result, df_answer)
209 |
210 | df_result = pdc.DataFrame({'a': a, 'b': b, 'c': c, 'd': d, 'e': e})
211 | df_result['f'] = True
212 | f = np.repeat(True, 3)
213 | df_answer = pdc.DataFrame({'a': a, 'b': b, 'c': c, 'd': d, 'e': e, 'f': f})
214 | assert_df_equals(df_result, df_answer)
215 |
216 | df_result = pdc.DataFrame({'a': a, 'b': b, 'c': c, 'd': d, 'e': e})
217 | f = np.array([1.5, 23, 4.11])
218 | df_result['c'] = f
219 | df_answer = pdc.DataFrame({'a': a, 'b': b, 'c': f, 'd': d, 'e': e})
220 | assert_df_equals(df_result, df_answer)
221 |
222 | with pytest.raises(NotImplementedError):
223 | df[['a', 'b']] = 5
224 |
225 | with pytest.raises(ValueError):
226 | df['a'] = np.random.rand(5, 5)
227 |
228 | with pytest.raises(ValueError):
229 | df['a'] = np.random.rand(5)
230 |
231 | with pytest.raises(ValueError):
232 | df['a'] = df[['a', 'b']]
233 |
234 | with pytest.raises(ValueError):
235 | df1 = pdc.DataFrame({'a': np.random.rand(5)})
236 | df['a'] = df1
237 |
238 | with pytest.raises(TypeError):
239 | df['a'] = set()
240 |
241 | def test_head_tail(self):
242 | df_result = df.head(2)
243 | df_answer = pdc.DataFrame({'a': a[:2], 'b': b[:2], 'c': c[:2],
244 | 'd': d[:2], 'e': e[:2]})
245 | assert_df_equals(df_result, df_answer)
246 |
247 | df_result = df.tail(2)
248 | df_answer = pdc.DataFrame({'a': a[-2:], 'b': b[-2:], 'c': c[-2:],
249 | 'd':d[-2:], 'e': e[-2:]})
250 | assert_df_equals(df_result, df_answer)
251 |
252 |
253 | a1 = np.array(['a', 'b', 'c'])
254 | b1 = np.array([11, 5, 8])
255 | c1 = np.array([3.4, np.nan, 5.1])
256 | df1 = pdc.DataFrame({'a': a1, 'b': b1, 'c': c1})
257 |
258 | a2 = np.array([True, False])
259 | b2 = np.array([True, True])
260 | c2 = np.array([False, True])
261 | df2 = pdc.DataFrame({'a': a2, 'b': b2, 'c': c2})
262 |
263 |
264 | class TestAggregation:
265 |
266 |
267 | def test_min(self):
268 | df_result = df1.min()
269 | df_answer = pdc.DataFrame({'a': np.array(['a'], dtype='O'),
270 | 'b': np.array([5]),
271 | 'c': np.array([np.nan])})
272 | assert_df_equals(df_result, df_answer)
273 |
274 | def test_max(self):
275 | df_result = df1.max()
276 | df_answer = pdc.DataFrame({'a': np.array(['c'], dtype='O'),
277 | 'b': np.array([11]),
278 | 'c': np.array([np.nan])})
279 | assert_df_equals(df_result, df_answer)
280 |
281 | def test_mean(self):
282 | df_result = df1.mean()
283 | df_answer = pdc.DataFrame({'b': np.array([8.]),
284 | 'c': np.array([np.nan])})
285 | assert_df_equals(df_result, df_answer)
286 |
287 | def test_median(self):
288 | df_result = df1.median()
289 | df_answer = pdc.DataFrame({'b': np.array([8]),
290 | 'c': np.array([np.nan])})
291 | assert_df_equals(df_result, df_answer)
292 |
293 | def test_sum(self):
294 | df_result = df1.sum()
295 | df_answer = pdc.DataFrame({'a': np.array(['abc'], dtype='O'),
296 | 'b': np.array([24]),
297 | 'c': np.array([np.nan])})
298 | assert_df_equals(df_result, df_answer)
299 |
300 | def test_var(self):
301 | df_result = df1.var()
302 | df_answer = pdc.DataFrame({'b': np.array([b1.var()]),
303 | 'c': np.array([np.nan])})
304 | assert_df_equals(df_result, df_answer)
305 |
306 | def test_std(self):
307 | df_result = df1.std()
308 | df_answer = pdc.DataFrame({'b': np.array([b1.std()]),
309 | 'c': np.array([np.nan])})
310 | assert_df_equals(df_result, df_answer)
311 |
312 | def test_all(self):
313 | df_result = df2.all()
314 | df_answer = pdc.DataFrame({'a': np.array([False]),
315 | 'b': np.array([True]),
316 | 'c': np.array([False])})
317 | assert_df_equals(df_result, df_answer)
318 |
319 | def test_any(self):
320 | df_result = df2.any()
321 | df_answer = pdc.DataFrame({'a': np.array([True]),
322 | 'b': np.array([True]),
323 | 'c': np.array([True])})
324 | assert_df_equals(df_result, df_answer)
325 |
326 | def test_argmax(self):
327 | df_result = df1.argmax()
328 | df_answer = pdc.DataFrame({'a': np.array([2]),
329 | 'b': np.array([0]),
330 | 'c': np.array([1])})
331 | assert_df_equals(df_result, df_answer)
332 |
333 | def test_argmin(self):
334 | df_result = df1.argmin()
335 | df_answer = pdc.DataFrame({'a': np.array([0]),
336 | 'b': np.array([1]),
337 | 'c': np.array([1])})
338 | assert_df_equals(df_result, df_answer)
339 |
340 |
341 | a3 = np.array(['a', None, 'c'])
342 | b3 = np.array([11, 5, 8])
343 | c3 = np.array([3.4, np.nan, 5.1])
344 | df3 = pdc.DataFrame({'a': a3, 'b': b3, 'c': c3})
345 |
346 | a4 = np.array(['a', 'a', 'c'], dtype='O')
347 | b4 = np.array([11, 5, 5])
348 | c4 = np.array([3.4, np.nan, 3.4])
349 | df4 = pdc.DataFrame({'a': a4, 'b': b4, 'c': c4})
350 |
351 |
352 | class TestOtherMethods:
353 |
354 | def test_isna(self):
355 | df_result = df3.isna()
356 | df_answer = pdc.DataFrame({'a': np.array([False, True, False]),
357 | 'b': np.array([False, False, False]),
358 | 'c': np.array([False, True, False])})
359 | assert_df_equals(df_result, df_answer)
360 |
361 | def test_count(self):
362 | df_result = df3.count()
363 | df_answer = pdc.DataFrame({'a': np.array([2]),
364 | 'b': np.array([3]),
365 | 'c': np.array([2])})
366 | assert_df_equals(df_result, df_answer)
367 |
368 | def test_unique(self):
369 | df_result = df4.unique()
370 | assert_array_equal(df_result[0].values[:, 0], np.unique(a4))
371 | assert_array_equal(df_result[1].values[:, 0], np.unique(b4))
372 | assert_array_equal(df_result[2].values[:, 0], np.unique(c4))
373 |
374 | def test_nunique(self):
375 | df_result = df4.nunique()
376 | df_answer = pdc.DataFrame({'a': np.array([2]),
377 | 'b': np.array([2]),
378 | 'c': np.array([2])})
379 | assert_df_equals(df_result, df_answer)
380 |
381 | def test_rename(self):
382 | df_result = df4.rename({'a': 'A', 'c': 'C'})
383 | df_answer = pdc.DataFrame({'A': a4, 'b': b4, 'C': c4})
384 | assert_df_equals(df_result, df_answer)
385 |
386 | def test_drop(self):
387 | df_result = df4.drop(['a', 'b'])
388 | df_answer = pdc.DataFrame({'c': c4})
389 | assert_df_equals(df_result, df_answer)
390 |
391 |
392 | a42 = np.array([-11, 5, 3])
393 | b42 = np.array([3.4, 5.1, -6])
394 | df42 = pdc.DataFrame({'a': a42, 'b': b42})
395 |
396 |
397 | class TestNonAgg:
398 |
399 | def test_abs(self):
400 | df_result = df42.abs()
401 | df_answer = pdc.DataFrame({'a': np.abs(a42), 'b': np.abs(b42)})
402 | assert_df_equals(df_result, df_answer)
403 |
404 | def test_cummin(self):
405 | df_result = df42.cummin()
406 | df_answer = pdc.DataFrame({'a': np.array([-11, -11, -11]),
407 | 'b': np.array([3.4, 3.4, -6])})
408 | assert_df_equals(df_result, df_answer)
409 |
410 | def test_cummax(self):
411 | df_result = df42.cummax()
412 | df_answer = pdc.DataFrame({'a': np.array([-11, 5, 5]),
413 | 'b': np.array([3.4, 5.1, 5.1])})
414 | assert_df_equals(df_result, df_answer)
415 |
416 | def test_cumsum(self):
417 | df_result = df42.cumsum()
418 | df_answer = pdc.DataFrame({'a': np.array([-11, -6, -3]),
419 | 'b': np.array([3.4, 8.5, 2.5])})
420 | assert_df_equals(df_result, df_answer)
421 |
422 | def test_clip(self):
423 | df_result = df42.clip(0, 4)
424 | df_answer = pdc.DataFrame({'a': np.array([0, 4, 3]),
425 | 'b': np.array([3.4, 4, 0])})
426 | assert_df_equals(df_result, df_answer)
427 |
428 | def test_round(self):
429 | df_result = df42.round(0)
430 | df_answer = pdc.DataFrame({'a': np.array([-11, 5, 3]),
431 | 'b': np.array([3, 5, -6])})
432 | assert_df_equals(df_result, df_answer)
433 |
434 | def test_copy(self):
435 | assert_df_equals(df42, df42.copy())
436 |
437 | def test_diff(self):
438 | df_result = df42.diff(1)
439 | df_answer = pdc.DataFrame({'a': np.array([np.nan, 16, -2]),
440 | 'b': np.array([np.nan, 1.7, -11.1])})
441 | assert_df_equals(df_result, df_answer)
442 |
443 | def test_pct_change(self):
444 | df_result = df42.pct_change(1)
445 | df_answer = pdc.DataFrame({'a': np.array([np.nan, 16 / -11, -2 / 5]),
446 | 'b': np.array([np.nan, 1.7 / 3.4, -11.1 / 5.1])})
447 | assert_df_equals(df_result, df_answer)
448 |
449 |
450 | a5 = np.array([11, 5])
451 | b5 = np.array([3.4, 5.1])
452 | df5 = pdc.DataFrame({'a': a5, 'b': b5})
453 |
454 |
455 | class TestOperators:
456 |
457 | def test_add(self):
458 | df_result = df5 + 3
459 | df_answer = pdc.DataFrame({'a': a5 + 3, 'b': b5 + 3})
460 | assert_df_equals(df_result, df_answer)
461 |
462 | df_result = 3 + df5
463 | assert_df_equals(df_result, df_answer)
464 |
465 | def test_sub(self):
466 | df_result = df5 - 3
467 | df_answer = pdc.DataFrame({'a': a5 - 3, 'b': b5 - 3})
468 | assert_df_equals(df_result, df_answer)
469 |
470 | df_result = 3 - df5
471 | df_answer = pdc.DataFrame({'a': 3 - a5, 'b': 3 - b5})
472 | assert_df_equals(df_result, df_answer)
473 |
474 | def test_mul(self):
475 | df_result = df5 * 3
476 | df_answer = pdc.DataFrame({'a': a5 * 3, 'b': b5 * 3})
477 | assert_df_equals(df_result, df_answer)
478 |
479 | df_result = 3 * df5
480 | assert_df_equals(df_result, df_answer)
481 |
482 | def test_truediv(self):
483 | df_result = df5 / 3
484 | df_answer = pdc.DataFrame({'a': a5 / 3, 'b': b5 / 3})
485 | assert_df_equals(df_result, df_answer)
486 |
487 | df_result = 3 / df5
488 | df_answer = pdc.DataFrame({'a': 3 / a5, 'b': 3 / b5})
489 | assert_df_equals(df_result, df_answer)
490 |
491 | def test_floordiv(self):
492 | df_result = df5 // 3
493 | df_answer = pdc.DataFrame({'a': a5 // 3, 'b': b5 // 3})
494 | assert_df_equals(df_result, df_answer)
495 |
496 | df_result = 3 // df5
497 | df_answer = pdc.DataFrame({'a': 3 // a5, 'b': 3 // b5})
498 | assert_df_equals(df_result, df_answer)
499 |
500 | def test_pow(self):
501 | df_result = df5 ** 3
502 | df_answer = pdc.DataFrame({'a': a5 ** 3, 'b': b5 ** 3})
503 | assert_df_equals(df_result, df_answer)
504 |
505 | df_result = 2 ** df5
506 | df_answer = pdc.DataFrame({'a': 2 ** a5, 'b': 2 ** b5})
507 | assert_df_equals(df_result, df_answer)
508 |
509 | def test_gt_lt(self):
510 | df_result = df5 > 3
511 | df_answer = pdc.DataFrame({'a': a5 > 3, 'b': b5 > 3})
512 | assert_df_equals(df_result, df_answer)
513 |
514 | df_result = df5 < 2
515 | df_answer = pdc.DataFrame({'a': a5 < 2, 'b': b5 < 2})
516 | assert_df_equals(df_result, df_answer)
517 |
518 | def test_ge_le(self):
519 | df_result = df5 >= 3
520 | df_answer = pdc.DataFrame({'a': a5 >= 3, 'b': b5 >= 3})
521 | assert_df_equals(df_result, df_answer)
522 |
523 | df_result = df5 < 2
524 | df_answer = pdc.DataFrame({'a': a5 <= 2, 'b': b5 <= 2})
525 | assert_df_equals(df_result, df_answer)
526 |
527 | def test_eq_ne(self):
528 | df_result = df5 == 3
529 | df_answer = pdc.DataFrame({'a': a5 == 3, 'b': b5 == 3})
530 | assert_df_equals(df_result, df_answer)
531 |
532 | df_result = df5 != 2
533 | df_answer = pdc.DataFrame({'a': a5 != 2, 'b': b5 != 2})
534 | assert_df_equals(df_result, df_answer)
535 |
536 |
537 | a6 = np.array(['b', 'c', 'a', 'a', 'b'])
538 | b6 = np.array([3.4, 5.1, 2, 1, 6])
539 | df6 = pdc.DataFrame({'a': a6, 'b': b6})
540 |
541 | a7 = np.array(['b', 'a', 'a', 'a', 'b'])
542 | b7 = np.array([3.4, 5.1, 2, 1, 6])
543 | df7 = pdc.DataFrame({'a': a7, 'b': b7})
544 |
545 |
546 | class TestMoreMethods:
547 |
548 | def test_sort_values(self):
549 | df_result = df6.sort_values('a')
550 | a = np.array(['a', 'a', 'b', 'b', 'c'])
551 | b = np.array([2, 1, 3.4, 6, 5.1])
552 | df_answer = pdc.DataFrame({'a': a, 'b': b})
553 | assert_df_equals(df_result, df_answer)
554 |
555 | def test_sort_values_desc(self):
556 | df_result = df6.sort_values('a', asc=False)
557 | a = np.array(['c', 'b', 'b', 'a', 'a'])
558 | b = np.array([5.1, 6, 3.4, 1,2])
559 | df_answer = pdc.DataFrame({'a': a, 'b': b})
560 | assert_df_equals(df_result, df_answer)
561 |
562 | def test_sort_values_two(self):
563 | df_result = df7.sort_values(['a', 'b'])
564 | a = np.array(['a', 'a', 'a', 'b', 'b'])
565 | b = np.array([1, 2, 5.1, 3.4, 6])
566 | df_answer = pdc.DataFrame({'a': a, 'b': b})
567 | assert_df_equals(df_result, df_answer)
568 |
569 | def test_sort_values_two_desc(self):
570 | df_result = df7.sort_values(['a', 'b'], asc=False)
571 | a = np.array(['a', 'a', 'a', 'b', 'b'])
572 | b = np.array([1, 2, 5.1, 3.4, 6])
573 | df_answer = pdc.DataFrame({'a': a[::-1], 'b': b[::-1]})
574 | assert_df_equals(df_result, df_answer)
575 |
576 | def test_sample(self):
577 | df_result = df7.sample(2, seed=1)
578 | df_answer = pdc.DataFrame({'a': np.array(['a', 'a'], dtype=object),
579 | 'b': np.array([2., 5.1])})
580 | assert_df_equals(df_result, df_answer)
581 |
582 | df_result = df7.sample(frac=.7, seed=1)
583 | df_answer = pdc.DataFrame({'a': np.array(['a', 'a', 'b'], dtype=object),
584 | 'b': np.array([2., 5.1, 6.])})
585 | assert_df_equals(df_result, df_answer)
586 |
587 | with pytest.raises(TypeError):
588 | df7.sample(2.5)
589 |
590 | with pytest.raises(ValueError):
591 | df7.sample(frac=-2)
592 |
593 |
594 | a8 = np.array(['b', 'a', 'a', 'a', 'b', 'a', 'a', 'b'])
595 | b8 = np.array(['B', 'A', 'A', 'A', 'B', 'B', 'B', 'A'])
596 | c8 = np.array([1, 2, 3, 4, 5, 6, 7, 8])
597 | df8 = pdc.DataFrame({'a': a8, 'b': b8, 'c': c8})
598 |
599 |
600 | class TestGrouping:
601 |
602 | def test_value_counts(self):
603 | df_temp = pdc.DataFrame({'state': np.array(['texas', 'texas', 'texas', 'florida', 'florida', 'florida', 'florida', 'ohio']),
604 | 'fruit': np.array(['a', 'a', 'a', 'a', 'b', 'b', 'b', 'a'])})
605 | df_results = df_temp.value_counts()
606 | df_answer = pdc.DataFrame({'state': np.array(['florida', 'texas', 'ohio'], dtype=object),
607 | 'count': np.array([4, 3, 1])})
608 | assert_df_equals(df_results[0], df_answer)
609 |
610 | df_answer = pdc.DataFrame({'fruit': np.array(['a', 'b'], dtype=object),
611 | 'count': np.array([5, 3])})
612 | assert_df_equals(df_results[1], df_answer)
613 |
614 | def test_value_counts_normalize(self):
615 | df_temp = pdc.DataFrame({'state': np.array(['texas', 'texas', 'texas', 'florida', 'florida', 'florida', 'florida', 'ohio']),
616 | 'fruit': np.array(['a', 'a', 'a', 'a', 'b', 'b', 'b', 'a'])})
617 | df_results = df_temp.value_counts(normalize=True)
618 | df_answer = pdc.DataFrame({'state': np.array(['florida', 'texas', 'ohio'], dtype=object),
619 | 'count': np.array([.5, .375, .125])})
620 | assert_df_equals(df_results[0], df_answer)
621 |
622 | df_answer = pdc.DataFrame({'fruit': np.array(['a', 'b'], dtype=object),
623 | 'count': np.array([.625, .375])})
624 | assert_df_equals(df_results[1], df_answer)
625 |
626 | def test_pivot_table_rows_or_cols(self):
627 | df_result = df8.pivot_table(rows='a')
628 | df_answer = pdc.DataFrame({'a': np.array(['a', 'b'], dtype=object),
629 | 'size': np.array([5, 3])})
630 | assert_df_equals(df_result, df_answer)
631 |
632 | df_result = df8.pivot_table(rows='a', values='c', aggfunc='sum')
633 | df_answer = pdc.DataFrame({'a': np.array(['a', 'b'], dtype=object),
634 | 'sum': np.array([22, 14])})
635 | assert_df_equals(df_result, df_answer)
636 |
637 | df_result = df8.pivot_table(columns='b')
638 | df_answer = pdc.DataFrame({'A': np.array([4]),
639 | 'B': np.array([4])})
640 | assert_df_equals(df_result, df_answer)
641 |
642 | df_result = df8.pivot_table(columns='a', values='c', aggfunc='sum')
643 | df_answer = pdc.DataFrame({'a': np.array([22]), 'b': np.array([14])})
644 | assert_df_equals(df_result, df_answer)
645 |
646 | def test_pivot_table_both(self):
647 | df_result = df8.pivot_table(rows='a', columns='b', values='c', aggfunc='sum')
648 | df_answer = pdc.DataFrame({'a': np.array(['a', 'b'], dtype=object),
649 | 'A': np.array([9., 8.]),
650 | 'B': np.array([13., 6.])})
651 | assert_df_equals(df_result, df_answer)
652 |
653 |
654 | movie = np.array(['field of dreams', 'star wars'], dtype='O')
655 | num = np.array(['5.1', '6'], dtype='O')
656 | df_string = pdc.DataFrame({'movie': movie, 'num': num})
657 |
658 |
659 | class TestStrings:
660 |
661 | def test_capitalize(self):
662 | result = df_string.str.capitalize('movie')
663 | movie = np.array(['Field of dreams', 'Star wars'], dtype='O')
664 | answer = pdc.DataFrame({'movie': movie})
665 | assert_df_equals(result, answer)
666 |
667 | def test_center(self):
668 | result = df_string.str.center('movie', 20, '-')
669 | movie = np.array(['--field of dreams---', '-----star wars------'], dtype='O')
670 | answer = pdc.DataFrame({'movie': movie})
671 | assert_df_equals(result, answer)
672 |
673 | def test_count(self):
674 | result = df_string.str.count('movie', 'e')
675 | movie = np.array([2, 0])
676 | answer = pdc.DataFrame({'movie': movie})
677 | assert_df_equals(result, answer)
678 |
679 | def test_startswith(self):
680 | result = df_string.str.startswith('movie', 'field')
681 | movie = np.array([True, False])
682 | answer = pdc.DataFrame({'movie': movie})
683 | assert_df_equals(result, answer)
684 |
685 | def test_endswith(self):
686 | result = df_string.str.endswith('movie', 's')
687 | movie = np.array([True, True])
688 | answer = pdc.DataFrame({'movie': movie})
689 | assert_df_equals(result, answer)
690 |
691 | def test_find(self):
692 | result = df_string.str.find('movie', 'ar')
693 | movie = np.array([-1, 2])
694 | answer = pdc.DataFrame({'movie': movie})
695 | assert_df_equals(result, answer)
696 |
697 | def test_len(self):
698 | result = df_string.str.len('movie')
699 | movie = np.array([15, 9])
700 | answer = pdc.DataFrame({'movie': movie})
701 | assert_df_equals(result, answer)
702 |
703 | def test_get(self):
704 | result = df_string.str.get('movie', 5)
705 | movie = np.array([' ', 'w'], dtype='O')
706 | answer = pdc.DataFrame({'movie': movie})
707 | assert_df_equals(result, answer)
708 |
709 | def test_index(self):
710 | with pytest.raises(ValueError):
711 | df_string.str.index('movie', 'z')
712 |
713 | def test_isalnum(self):
714 | result = df_string.str.isalnum('num')
715 | num = np.array([False, True])
716 | answer = pdc.DataFrame({'num': num})
717 | assert_df_equals(result, answer)
718 |
719 | def test_isalpha(self):
720 | result = df_string.str.isalpha('num')
721 | num = np.array([False, False])
722 | answer = pdc.DataFrame({'num': num})
723 | assert_df_equals(result, answer)
724 |
725 | def test_isdecimal(self):
726 | result = df_string.str.isdecimal('num')
727 | num = np.array([False, True])
728 | answer = pdc.DataFrame({'num': num})
729 | assert_df_equals(result, answer)
730 |
731 | def test_isnumeric(self):
732 | result = df_string.str.isnumeric('num')
733 | num = np.array([False, True])
734 | answer = pdc.DataFrame({'num': num})
735 | assert_df_equals(result, answer)
736 |
737 | def test_islower(self):
738 | result = df_string.str.islower('movie')
739 | movie = np.array([True, True])
740 | answer = pdc.DataFrame({'movie': movie})
741 | assert_df_equals(result, answer)
742 |
743 | def test_isupper(self):
744 | result = df_string.str.isupper('movie')
745 | movie = np.array([False, False])
746 | answer = pdc.DataFrame({'movie': movie})
747 | assert_df_equals(result, answer)
748 |
749 | def test_isspace(self):
750 | result = df_string.str.isspace('num')
751 | num = np.array([False, False])
752 | answer = pdc.DataFrame({'num': num})
753 | assert_df_equals(result, answer)
754 |
755 | def test_istitle(self):
756 | result = df_string.str.istitle('num')
757 | num = np.array([False, False])
758 | answer = pdc.DataFrame({'num': num})
759 | assert_df_equals(result, answer)
760 |
761 | def test_lstrip(self):
762 | result = df_string.str.lstrip('movie', 'fies')
763 | movie = np.array(['ld of dreams', 'tar wars'], dtype='O')
764 | answer = pdc.DataFrame({'movie': movie})
765 | assert_df_equals(result, answer)
766 |
767 | def test_rstrip(self):
768 | result = df_string.str.rstrip('movie', 's')
769 | movie = np.array(['field of dream', 'star war'], dtype='O')
770 | answer = pdc.DataFrame({'movie': movie})
771 | assert_df_equals(result, answer)
772 |
773 | def test_strip(self):
774 | result = df_string.str.strip('movie', 'fs')
775 | movie = np.array(['ield of dream', 'tar war'], dtype='O')
776 | answer = pdc.DataFrame({'movie': movie})
777 | assert_df_equals(result, answer)
778 |
779 | def test_replace(self):
780 | result = df_string.str.replace('movie', 's', 'Z')
781 | movie = np.array(['field of dreamZ', 'Ztar warZ'], dtype='O')
782 | answer = pdc.DataFrame({'movie': movie})
783 | assert_df_equals(result, answer)
784 |
785 | def test_swapcase(self):
786 | result = df_string.str.swapcase('movie')
787 | movie = np.array(['FIELD OF DREAMS', 'STAR WARS'], dtype='O')
788 | answer = pdc.DataFrame({'movie': movie})
789 | assert_df_equals(result, answer)
790 |
791 | def test_title(self):
792 | result = df_string.str.title('movie')
793 | movie = np.array(['Field Of Dreams', 'Star Wars'], dtype='O')
794 | answer = pdc.DataFrame({'movie': movie})
795 | assert_df_equals(result, answer)
796 |
797 | def test_upper(self):
798 | result = df_string.str.upper('movie')
799 | movie = np.array(['FIELD OF DREAMS', 'STAR WARS'], dtype='O')
800 | answer = pdc.DataFrame({'movie': movie})
801 | assert_df_equals(result, answer)
802 |
803 | def test_zfill(self):
804 | result = df_string.str.zfill('movie', 16)
805 | movie = np.array(['0field of dreams', '0000000star wars'], dtype='O')
806 | answer = pdc.DataFrame({'movie': movie})
807 | assert_df_equals(result, answer)
808 |
809 |
810 | df_emp = pdc.read_csv('data/employee.csv')
811 |
812 |
813 | class TestReadCSV:
814 |
815 | def test_columns(self):
816 | result = df_emp.columns
817 | answer = ['dept', 'race', 'gender', 'salary']
818 | assert result == answer
819 |
820 | def test_data_types(self):
821 | df_result = df_emp.dtypes
822 | cols = np.array(['dept', 'race', 'gender', 'salary'], dtype='O')
823 | dtypes = np.array(['string', 'string', 'string', 'int'], dtype='O')
824 | df_answer = pdc.DataFrame({'Column Name': cols,
825 | 'Data Type': dtypes})
826 | assert_df_equals(df_result, df_answer)
827 |
828 | def test_sum(self):
829 | result = df_emp['salary'].sum()
830 | answer = 86387875
831 | assert result == answer
832 |
833 | def test_head(self):
834 | data = {'dept': np.array(['Houston Police Department-HPD',
835 | 'Houston Fire Department (HFD)',
836 | 'Houston Police Department-HPD',
837 | 'Public Works & Engineering-PWE',
838 | 'Houston Airport System (HAS)'], dtype='O'),
839 | 'race': np.array(['White', 'White', 'Black', 'Asian', 'White'], dtype='O'),
840 | 'gender': np.array(['Male', 'Male', 'Male', 'Male', 'Male'], dtype='O'),
841 | 'salary': np.array([45279, 63166, 66614, 71680, 42390])}
842 | result = df_emp.head()
843 | answer = pdc.DataFrame(data)
844 | assert_df_equals(result, answer)
--------------------------------------------------------------------------------
/pandas_cub_final/__init__.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | __version__ = '0.0.1'
4 |
5 |
6 | class DataFrame:
7 |
8 | def __init__(self, data):
9 | """
10 | A DataFrame holds two-dimensional heterogeneous data. Create it by
11 | passing a dictionary of NumPy arrays to the values parameter
12 |
13 | Parameters
14 | ----------
15 | data: dict
16 | A dictionary of strings mapped to NumPy arrays. The key will
17 | become the column name.
18 | """
19 | # check for correct input types
20 | self._check_input_types(data)
21 |
22 | # check for equal array lengths
23 | self._check_array_lengths(data)
24 |
25 | # convert unicode arrays to object
26 | self._data = self._convert_unicode_to_object(data)
27 |
28 | # Allow for special methods for strings
29 | self.str = StringMethods(self)
30 | self._add_docs()
31 |
32 | def _check_input_types(self, data):
33 | if not isinstance(data, dict):
34 | raise TypeError("`data` must be a dictionary of 1-D NumPy arrays")
35 |
36 | for col_name, values in data.items():
37 | if not isinstance(col_name, str):
38 | raise TypeError('All column names must be a string')
39 | if not isinstance(values, np.ndarray):
40 | raise TypeError('All values must be a 1-D NumPy array')
41 | else:
42 | if values.ndim != 1:
43 | raise ValueError('Each value must be a 1-D NumPy array')
44 |
45 | def _check_array_lengths(self, data):
46 | for i, values in enumerate(data.values()):
47 | if i == 0:
48 | length = len(values)
49 | if length != len(values):
50 | raise ValueError('All values must be the same length')
51 |
52 | def _convert_unicode_to_object(self, data):
53 | new_data = {}
54 | for col_name, values in data.items():
55 | if values.dtype.kind == 'U':
56 | new_data[col_name] = values.astype('O')
57 | else:
58 | new_data[col_name] = values
59 | return new_data
60 |
61 | def __len__(self):
62 | """
63 | Make the builtin len function work with our dataframe
64 |
65 | Returns
66 | -------
67 | int: the number of rows in the dataframe
68 | """
69 | return len(next(iter(self._data.values())))
70 |
71 | @property
72 | def columns(self):
73 | """
74 | _data holds column names mapped to arrays
75 | take advantage of internal ordering of dictionaries to
76 | put columns in correct order in list. Only works in 3.6+
77 |
78 | Returns
79 | -------
80 | list of column names
81 | """
82 | return list(self._data)
83 |
84 | @columns.setter
85 | def columns(self, columns):
86 | """
87 | Must supply a list of columns as strings the same length
88 | as the current DataFrame
89 |
90 | Parameters
91 | ----------
92 | columns: list of strings
93 |
94 | Returns
95 | -------
96 | Nones
97 | """
98 | if not isinstance(columns, list):
99 | raise TypeError('New columns must be a list')
100 | if len(columns) != len(self.columns):
101 | raise ValueError(f'New column length must be {len(self._data)}')
102 | else:
103 | for col in columns:
104 | if not isinstance(col, str):
105 | raise TypeError('New column names must be strings')
106 | if len(columns) != len(set(columns)):
107 | raise ValueError('Column names must be unique')
108 |
109 | new_data = dict(zip(columns, self._data.values()))
110 | self._data = new_data
111 |
112 | @property
113 | def shape(self):
114 | """
115 | Returns
116 | -------
117 | two-item tuple of number of rows and columns
118 | """
119 | return len(self), len(self.columns)
120 |
121 | def _repr_html_(self):
122 | """
123 | Used to create a string of HTML to nicely display the DataFrame
124 | in a Jupyter Notebook. Different string formatting is used for
125 | different data types.
126 |
127 | The structure of the HTML is as follows:
128 |
129 |
130 |
131 | | data |
132 | ...
133 | data |
134 |
135 |
136 |
137 |
138 | | {i} |
139 | data |
140 | ...
141 | data |
142 |
143 | ...
144 |
145 | | {i} |
146 | data |
147 | ...
148 | data |
149 |
150 |
151 |
152 | """
153 | html = ' | '
154 | for col in self.columns:
155 | html += f"{col:10} | "
156 |
157 | html += '
'
158 | html += ""
159 |
160 | only_head = False
161 | num_head = 10
162 | num_tail = 10
163 | if len(self) <= 20:
164 | only_head = True
165 | num_head = len(self)
166 |
167 | for i in range(num_head):
168 | html += f'| {i} | '
169 | for col, values in self._data.items():
170 | kind = values.dtype.kind
171 | if kind == 'f':
172 | html += f'{values[i]:10.3f} | '
173 | elif kind == 'b':
174 | html += f'{values[i]} | '
175 | elif kind == 'O':
176 | v = values[i]
177 | if v is None:
178 | v = 'None'
179 | html += f'{v:10} | '
180 | else:
181 | html += f'{values[i]:10} | '
182 | html += '
'
183 |
184 | if not only_head:
185 | html += '| ... | '
186 | for i in range(len(self.columns)):
187 | html += '... | '
188 | html += '
'
189 | for i in range(-num_tail, 0):
190 | html += f'| {len(self) + i} | '
191 | for col, values in self._data.items():
192 | kind = values.dtype.kind
193 | if kind == 'f':
194 | html += f'{values[i]:10.3f} | '
195 | elif kind == 'b':
196 | html += f'{values[i]} | '
197 | elif kind == 'O':
198 | v = values[i]
199 | if v is None:
200 | v = 'None'
201 | html += f'{v:10} | '
202 | else:
203 | html += f'{values[i]:10} | '
204 | html += '
'
205 |
206 | html += '
'
207 | return html
208 |
209 | @property
210 | def values(self):
211 | """
212 | Returns
213 | -------
214 | A single 2D NumPy array of the underlying data
215 | """
216 | return np.column_stack(self._data.values())
217 |
218 | @property
219 | def dtypes(self):
220 | """
221 | Returns
222 | -------
223 | A two-column DataFrame of column names in one column and
224 | their data type in the other
225 | """
226 | DTYPE_NAME = {'O': 'string', 'i': 'int', 'f': 'float', 'b': 'bool'}
227 | col_arr = np.array(self.columns)
228 | dtypes = []
229 | for values in self._data.values():
230 | kind = values.dtype.kind
231 | dtype = DTYPE_NAME[kind]
232 | dtypes.append(dtype)
233 |
234 | return DataFrame({'Column Name': col_arr, 'Data Type': np.array(dtypes)})
235 |
236 | def __getitem__(self, item):
237 | """
238 | Use the brackets operator to simultaneously select rows and columns
239 |
240 | A single string selects one column -> df['colname']
241 | A list of strings selects multiple columns -> df[['colname1', 'colname2']]
242 | A one column DataFrame of booleans that filters rows -> df[df_bool]
243 |
244 | Row and column selection simultaneously -> df[rs, cs]
245 | where cs and rs can be integers, slices, or a list of integers
246 | rs can also be a one-column boolean DataFrame
247 |
248 | Returns
249 | -------
250 | A subset of the original DataFrame
251 | """
252 | # select a single column -> df['colname']
253 | if isinstance(item, str):
254 | return DataFrame({item: self._data[item]})
255 |
256 | # select multiple columns -> df[['colname1', 'colname2']]
257 | if isinstance(item, list):
258 | return DataFrame({col: self._data[col] for col in item})
259 |
260 | # boolean selection
261 | if isinstance(item, DataFrame):
262 | if item.shape[1] != 1:
263 | raise ValueError('Can only pass a one column DataFrame for selection')
264 |
265 | bool_arr = next(iter(item._data.values()))
266 | if bool_arr.dtype.kind != 'b':
267 | raise TypeError('DataFrame must be a boolean')
268 |
269 | new_data = {}
270 | for col, values in self._data.items():
271 | new_data[col] = values[bool_arr]
272 | return DataFrame(new_data)
273 |
274 | if isinstance(item, tuple):
275 | return self._getitem_tuple(item)
276 | else:
277 | raise TypeError('Select with either a string, a list, or a row and column '
278 | 'simultaneous selection')
279 |
280 | def _getitem_tuple(self, item):
281 | # simultaneous selection of rows and cols -> df[rs, cs]
282 | if len(item) != 2:
283 | raise ValueError('Pass either a single string or a two-item tuple inside the '
284 | 'selection operator.')
285 | row_selection = item[0]
286 | col_selection = item[1]
287 | if isinstance(row_selection, int):
288 | row_selection = [row_selection]
289 | elif isinstance(row_selection, DataFrame):
290 | if row_selection.shape[1] != 1:
291 | raise ValueError('Can only pass a one column DataFrame for selection')
292 | row_selection = next(iter(row_selection._data.values()))
293 | if row_selection.dtype.kind != 'b':
294 | raise TypeError('DataFrame must be a boolean')
295 | elif not isinstance(row_selection, (list, slice)):
296 | raise TypeError('Row selection must be either an int, slice, list, or DataFrame')
297 |
298 | if isinstance(col_selection, int):
299 | col_selection = [self.columns[col_selection]]
300 | elif isinstance(col_selection, str):
301 | col_selection = [col_selection]
302 | elif isinstance(col_selection, list):
303 | new_col_selction = []
304 | for col in col_selection:
305 | if isinstance(col, int):
306 | new_col_selction.append(self.columns[col])
307 | else:
308 | new_col_selction.append(col)
309 | col_selection = new_col_selction
310 | elif isinstance(col_selection, slice):
311 | start = col_selection.start
312 | stop = col_selection.stop
313 | step = col_selection.step
314 | if isinstance(start, str):
315 | start = self.columns.index(col_selection.start)
316 | if isinstance(stop, str):
317 | stop = self.columns.index(col_selection.stop) + 1
318 |
319 | col_selection = self.columns[start:stop:step]
320 | else:
321 | raise TypeError('Column selection must be either an int, string, list, or slice')
322 |
323 | new_data = {}
324 | for col in col_selection:
325 | new_data[col] = self._data[col][row_selection]
326 | return DataFrame(new_data)
327 |
328 | def _ipython_key_completions_(self):
329 | # allows for tab completion when doing df['c
330 | return self.columns
331 |
332 | def __setitem__(self, key, value):
333 | # adds a new column or a overwrites an old column
334 | if not isinstance(key, str):
335 | raise NotImplementedError('Only able to set a single column')
336 |
337 | if isinstance(value, np.ndarray):
338 | if value.ndim != 1:
339 | raise ValueError('Setting array must be 1D')
340 | if len(value) != len(self):
341 | raise ValueError('Setting array must be same length as DataFrame')
342 | elif isinstance(value, DataFrame):
343 | if value.shape[1] != 1:
344 | raise ValueError('Setting DataFrame must be one column')
345 | if len(value) != len(self):
346 | raise ValueError('Setting and Calling DataFrames must be the same length')
347 | value = next(iter(value._data.values()))
348 | elif isinstance(value, (int, str, float, bool)):
349 | value = np.repeat(value, len(self))
350 | else:
351 | raise TypeError('Setting value must either be a numpy array, '
352 | 'DataFrame, integer, string, float, or boolean')
353 |
354 | if value.dtype.kind == 'U':
355 | value = value.astype('O')
356 |
357 | self._data[key] = value
358 |
359 | def head(self, n=5):
360 | """
361 | Return the first n rows
362 |
363 | Parameters
364 | ----------
365 | n: int
366 |
367 | Returns
368 | -------
369 | DataFrame
370 | """
371 | return self[:n, :]
372 |
373 | def tail(self, n=5):
374 | """
375 | Return the last n rows
376 |
377 | Parameters
378 | ----------
379 | n: int
380 |
381 | Returns
382 | -------
383 | DataFrame
384 | """
385 | return self[-n:, :]
386 |
387 | #### Aggregation Methods ####
388 |
389 | def min(self):
390 | return self._agg(np.min)
391 |
392 | def max(self):
393 | return self._agg(np.max)
394 |
395 | def mean(self):
396 | return self._agg(np.mean)
397 |
398 | def median(self):
399 | return self._agg(np.median)
400 |
401 | def sum(self):
402 | return self._agg(np.sum)
403 |
404 | def var(self):
405 | return self._agg(np.var)
406 |
407 | def std(self):
408 | return self._agg(np.std)
409 |
410 | def all(self):
411 | return self._agg(np.all)
412 |
413 | def any(self):
414 | return self._agg(np.any)
415 |
416 | def argmax(self):
417 | return self._agg(np.argmax)
418 |
419 | def argmin(self):
420 | return self._agg(np.argmin)
421 |
422 | def _agg(self, aggfunc):
423 | """
424 | Generic aggregation function that applies the
425 | aggregation to each column
426 |
427 | Parameters
428 | ----------
429 | aggfunc: str of the aggregation function name in NumPy
430 |
431 | Returns
432 | -------
433 | A DataFrame
434 | """
435 | new_data = {}
436 | for col, values in self._data.items():
437 | try:
438 | val = aggfunc(values)
439 | except TypeError:
440 | continue
441 | new_data[col] = np.array([val])
442 | return DataFrame(new_data)
443 |
444 | def isna(self):
445 | """
446 | Determines whether each value in the DataFrame is missing or not
447 |
448 | Returns
449 | -------
450 | A DataFrame of booleans the same size as the calling DataFrame
451 | """
452 | new_data = {}
453 | for col, values in self._data.items():
454 | kind = values.dtype.kind
455 | if kind == 'O':
456 | new_data[col] = values == None
457 | else:
458 | new_data[col] = np.isnan(values)
459 | return DataFrame(new_data)
460 |
461 | def count(self):
462 | """
463 | Counts the number of non-missing values per column
464 |
465 | Returns
466 | -------
467 | A DataFrame
468 | """
469 | new_data = {}
470 | df = self.isna()
471 | length = len(self)
472 | for col, values in df._data.items():
473 | val = length - values.sum()
474 | new_data[col] = np.array([val])
475 | return DataFrame(new_data)
476 |
477 | def unique(self):
478 | """
479 | Finds the unique values of each column
480 |
481 | Returns
482 | -------
483 | A list of one-column DataFrames
484 | """
485 | dfs = []
486 | for col, values in self._data.items():
487 | uniques = np.unique(values)
488 | dfs.append(DataFrame({col: uniques}))
489 | if len(dfs) == 1:
490 | return dfs[0]
491 | return dfs
492 |
493 | def nunique(self):
494 | """
495 | Find the number of unique values in each column
496 |
497 | Returns
498 | -------
499 | A DataFrame
500 | """
501 | new_data = {}
502 | for col, value in self._data.items():
503 | new_data[col] = np.array([len(np.unique(value))])
504 | return DataFrame(new_data)
505 |
506 | def value_counts(self, normalize=False):
507 | """
508 | Returns the frequency of each unique value for each column
509 |
510 | Parameters
511 | ----------
512 | normalize: bool
513 | If True, returns the relative frequencies (percent)
514 |
515 | Returns
516 | -------
517 | A list of DataFrames or a single DataFrame if one column
518 | """
519 | dfs = []
520 | for col, values in self._data.items():
521 | keys, raw_counts = np.unique(values, return_counts=True)
522 |
523 | order = np.argsort(-raw_counts)
524 | keys = keys[order]
525 | raw_counts = raw_counts[order]
526 |
527 | if normalize:
528 | raw_counts = raw_counts / raw_counts.sum()
529 | df = DataFrame({col: keys, 'count': raw_counts})
530 | dfs.append(df)
531 | if len(dfs) == 1:
532 | return dfs[0]
533 | return dfs
534 |
535 | def rename(self, columns):
536 | """
537 | Renames columns in the DataFrame
538 |
539 | Parameters
540 | ----------
541 | columns: dict
542 | A dictionary mapping the old column name to the new column name
543 |
544 | Returns
545 | -------
546 | A DataFrame
547 | """
548 | if not isinstance(columns, dict):
549 | raise TypeError('`columns` must be a dictionary')
550 |
551 | new_data = {}
552 | for col, values in self._data.items():
553 | new_data[columns.get(col, col)] = values
554 | return DataFrame(new_data)
555 |
556 | def drop(self, columns):
557 | """
558 | Drops one or more columns from a DataFrame
559 |
560 | Parameters
561 | ----------
562 | columns: str or list of strings
563 |
564 | Returns
565 | -------
566 | A DataFrame
567 | """
568 | if isinstance(columns, str):
569 | columns = [columns]
570 | elif not isinstance(columns, list):
571 | raise TypeError('`columns` must be either a string or a list')
572 | new_data = {}
573 | for col, values in self._data.items():
574 | if col not in columns:
575 | new_data[col] = values
576 | return DataFrame(new_data)
577 |
578 | #### Non-Aggregation Methods ####
579 |
580 | def abs(self):
581 | """
582 | Takes the absolute value of each value in the DataFrame
583 |
584 | Returns
585 | -------
586 | A DataFrame
587 | """
588 | return self._non_agg(np.abs)
589 |
590 | def cummin(self):
591 | """
592 | Finds cumulative minimum by column
593 |
594 | Returns
595 | -------
596 | A DataFrame
597 | """
598 | return self._non_agg(np.minimum.accumulate)
599 |
600 | def cummax(self):
601 | """
602 | Finds cumulative maximum by column
603 |
604 | Returns
605 | -------
606 | A DataFrame
607 | """
608 | return self._non_agg(np.maximum.accumulate)
609 |
610 | def cumsum(self):
611 | """
612 | Finds cumulative sum by column
613 |
614 | Returns
615 | -------
616 | A DataFrame
617 | """
618 | return self._non_agg(np.cumsum)
619 |
620 | def clip(self, lower=None, upper=None):
621 | """
622 | All values less than lower will be set to lower
623 | All values greater than upper will be set to upper
624 |
625 | Parameters
626 | ----------
627 | lower: number or None
628 | upper: number or None
629 |
630 | Returns
631 | -------
632 | A DataFrame
633 | """
634 | return self._non_agg(np.clip, a_min=lower, a_max=upper)
635 |
636 | def round(self, n):
637 | """
638 | Rounds values to the nearest n decimals
639 |
640 | Returns
641 | -------
642 | A DataFrame
643 | """
644 | return self._non_agg(np.round, 'if', decimals=n)
645 |
646 | def copy(self):
647 | """
648 | Copies the DataFrame
649 |
650 | Returns
651 | -------
652 | A DataFrame
653 | """
654 | return self._non_agg(np.copy)
655 |
656 | def _non_agg(self, funcname, kinds='bif', **kwargs):
657 | """
658 | Generic non-aggregation function
659 |
660 | Parameters
661 | ----------
662 | funcname: numpy function
663 | args: extra arguments for certain functions
664 |
665 | Returns
666 | -------
667 | A DataFrame
668 | """
669 | new_data = {}
670 | for col, values in self._data.items():
671 | if values.dtype.kind in kinds:
672 | values = funcname(values, **kwargs)
673 | else:
674 | values = values.copy()
675 | new_data[col] = values
676 | return DataFrame(new_data)
677 |
678 | def diff(self, n=1):
679 | """
680 | Take the difference between the current value and
681 | the nth value above it.
682 |
683 | Parameters
684 | ----------
685 | n: int
686 |
687 | Returns
688 | -------
689 | A DataFrame
690 | """
691 | def func(values):
692 | values = values.astype('float')
693 | values_shifted = np.roll(values, n)
694 | values = values - values_shifted
695 | if n >= 0:
696 | values[:n] = np.NAN
697 | else:
698 | values[n:] = np.NAN
699 | return values
700 | return self._non_agg(func)
701 |
702 | def pct_change(self, n=1):
703 | """
704 | Take the percentage difference between the current value and
705 | the nth value above it.
706 |
707 | Parameters
708 | ----------
709 | n: int
710 |
711 | Returns
712 | -------
713 | A DataFrame
714 | """
715 | def func(values):
716 | values = values.astype('float')
717 | values_shifted = np.roll(values, n)
718 | values = values - values_shifted
719 | if n >= 0:
720 | values[:n] = np.NAN
721 | else:
722 | values[n:] = np.NAN
723 | return values / values_shifted
724 | return self._non_agg(func)
725 |
726 | #### Arithmetic and Comparison Operators ####
727 |
728 | def __add__(self, other):
729 | return self._oper('__add__', other)
730 |
731 | def __radd__(self, other):
732 | return self._oper('__radd__', other)
733 |
734 | def __sub__(self, other):
735 | return self._oper('__sub__', other)
736 |
737 | def __rsub__(self, other):
738 | return self._oper('__rsub__', other)
739 |
740 | def __mul__(self, other):
741 | return self._oper('__mul__', other)
742 |
743 | def __rmul__(self, other):
744 | return self._oper('__rmul__', other)
745 |
746 | def __truediv__(self, other):
747 | return self._oper('__truediv__', other)
748 |
749 | def __rtruediv__(self, other):
750 | return self._oper('__rtruediv__', other)
751 |
752 | def __floordiv__(self, other):
753 | return self._oper('__floordiv__', other)
754 |
755 | def __rfloordiv__(self, other):
756 | return self._oper('__rfloordiv__', other)
757 |
758 | def __pow__(self, other):
759 | return self._oper('__pow__', other)
760 |
761 | def __rpow__(self, other):
762 | return self._oper('__rpow__', other)
763 |
764 | def __gt__(self, other):
765 | return self._oper('__gt__', other)
766 |
767 | def __lt__(self, other):
768 | return self._oper('__lt__', other)
769 |
770 | def __ge__(self, other):
771 | return self._oper('__ge__', other)
772 |
773 | def __le__(self, other):
774 | return self._oper('__le__', other)
775 |
776 | def __ne__(self, other):
777 | return self._oper('__ne__', other)
778 |
779 | def __eq__(self, other):
780 | return self._oper('__eq__', other)
781 |
782 | def _oper(self, op, other):
783 | """
784 | Generic operator method
785 |
786 | Parameters
787 | ----------
788 | op: str name of special method
789 | other: the other object being operated on
790 |
791 | Returns
792 | -------
793 | A DataFrame
794 | """
795 | if isinstance(other, DataFrame):
796 | if other.shape[1] != 1:
797 | raise ValueError('`other` must be a one-column DataFrame')
798 | other = next(iter(other._data.values()))
799 | new_data = {}
800 | for col, values in self._data.items():
801 | func = getattr(values, op)
802 | new_data[col] = func(other)
803 | return DataFrame(new_data)
804 |
805 | def sort_values(self, by, asc=True):
806 | """
807 | Sort the DataFrame by one or more values
808 |
809 | Parameters
810 | ----------
811 | by: str or list of column names
812 | asc: boolean of sorting order
813 |
814 | Returns
815 | -------
816 | A DataFrame
817 | """
818 | if isinstance(by, str):
819 | order = np.argsort(self._data[by])
820 | elif isinstance(by, list):
821 | cols = [self._data[col] for col in by[::-1]]
822 | order = np.lexsort(cols)
823 | else:
824 | raise TypeError('`by` must be a str or a list')
825 |
826 | if not asc:
827 | order = order[::-1]
828 | return self[order.tolist(), :]
829 |
830 | def sample(self, n=None, frac=None, replace=False, seed=None):
831 | """
832 | Randomly samples rows the DataFrame
833 |
834 | Parameters
835 | ----------
836 | n: int
837 | number of rows to return
838 | frac: float
839 | Proportion of the data to sample
840 | replace: bool
841 | Whether or not to sample with replacement
842 | seed: int
843 | Seeds the random number generator
844 |
845 | Returns
846 | -------
847 | A DataFrame
848 | """
849 | if seed:
850 | np.random.seed(seed)
851 | if frac is not None:
852 | if frac <= 0:
853 | raise ValueError('`frac` must be positive')
854 | n = int(frac * len(self))
855 | if n is not None:
856 | if not isinstance(n, int):
857 | raise TypeError('`n` must be an int')
858 | rows = np.random.choice(np.arange(len(self)), size=n, replace=replace).tolist()
859 | return self[rows, :]
860 |
861 | def pivot_table(self, rows=None, columns=None, values=None, aggfunc=None):
862 | """
863 | Creates a pivot table from one or two 'grouping' columns.
864 |
865 | Parameters
866 | ----------
867 | rows: str of column name to group by
868 | Optional
869 | columns: str of column name to group by
870 | Optional
871 | values: str of column name to aggregate
872 | Required
873 | aggfunc: str of aggregation function
874 |
875 | Returns
876 | -------
877 | A DataFrame
878 | """
879 | if rows is None and columns is None:
880 | raise ValueError('`rows` or `columns` cannot both be `None`')
881 |
882 | if values is not None:
883 | val_data = self._data[values]
884 | if aggfunc is None:
885 | raise ValueError('You must provide `aggfunc` when `values` is provided.')
886 | else:
887 | if aggfunc is None:
888 | aggfunc = 'size'
889 | val_data = np.empty(len(self))
890 | else:
891 | raise ValueError('You cannot provide `aggfunc` when `values` is None')
892 |
893 | if rows is not None:
894 | row_data = self._data[rows]
895 |
896 | if columns is not None:
897 | col_data = self._data[columns]
898 |
899 | if rows is None:
900 | pivot_type = 'columns'
901 | elif columns is None:
902 | pivot_type = 'rows'
903 | else:
904 | pivot_type = 'all'
905 |
906 | from collections import defaultdict
907 | d = defaultdict(list)
908 | if pivot_type == 'columns':
909 | for group, val in zip(col_data, val_data):
910 | d[group].append(val)
911 | elif pivot_type == 'rows':
912 | for group, val in zip(row_data, val_data):
913 | d[group].append(val)
914 | else:
915 | for group1, group2, val in zip(row_data, col_data, val_data):
916 | d[(group1, group2)].append(val)
917 |
918 | agg_dict = {}
919 | for group, vals in d.items():
920 | arr = np.array(vals)
921 | func = getattr(np, aggfunc)
922 | agg_dict[group] = func(arr)
923 |
924 | new_data = {}
925 | if pivot_type == 'columns':
926 | for col_name in sorted(agg_dict):
927 | value = agg_dict[col_name]
928 | new_data[col_name] = np.array([value])
929 | elif pivot_type == 'rows':
930 | row_array = np.array(list(agg_dict.keys()))
931 | val_array = np.array(list(agg_dict.values()))
932 |
933 | order = np.argsort(row_array)
934 | new_data[rows] = row_array[order]
935 | new_data[aggfunc] = val_array[order]
936 | else:
937 | row_set = set()
938 | col_set = set()
939 | for group in agg_dict:
940 | row_set.add(group[0])
941 | col_set.add(group[1])
942 | row_list = sorted(row_set)
943 | col_list = sorted(col_set)
944 | new_data = {}
945 | new_data[rows] = np.array(row_list)
946 | for col in col_list:
947 | new_vals = []
948 | for row in row_list:
949 | new_val = agg_dict.get((row, col), np.nan)
950 | new_vals.append(new_val)
951 | new_data[col] = np.array(new_vals)
952 | return DataFrame(new_data)
953 |
954 | def _add_docs(self):
955 | agg_names = ['min', 'max', 'mean', 'median', 'sum', 'var',
956 | 'std', 'any', 'all', 'argmax', 'argmin']
957 | agg_doc = \
958 | """
959 | Find the {} of each column
960 |
961 | Returns
962 | -------
963 | DataFrame
964 | """
965 | for name in agg_names:
966 | getattr(DataFrame, name).__doc__ = agg_doc.format(name)
967 |
968 |
969 | class StringMethods:
970 |
971 | def __init__(self, df):
972 | self._df = df
973 |
974 | def capitalize(self, col):
975 | return self._str_method(str.capitalize, col)
976 |
977 | def center(self, col, width, fillchar=None):
978 | if fillchar is None:
979 | fillchar = ' '
980 | return self._str_method(str.center, col, width, fillchar)
981 |
982 | def count(self, col, sub, start=None, stop=None):
983 | return self._str_method(str.count, col, sub, start, stop)
984 |
985 | def endswith(self, col, suffix, start=None, stop=None):
986 | return self._str_method(str.endswith, col, suffix, start, stop)
987 |
988 | def startswith(self, col, suffix, start=None, stop=None):
989 | return self._str_method(str.startswith, col, suffix, start, stop)
990 |
991 | def find(self, col, sub, start=None, stop=None):
992 | return self._str_method(str.find, col, sub, start, stop)
993 |
994 | def len(self, col):
995 | return self._str_method(str.__len__, col)
996 |
997 | def get(self, col, item):
998 | return self._str_method(str.__getitem__, col, item)
999 |
1000 | def index(self, col, sub, start=None, stop=None):
1001 | return self._str_method(str.index, col, sub, start, stop)
1002 |
1003 | def isalnum(self, col):
1004 | return self._str_method(str.isalnum, col)
1005 |
1006 | def isalpha(self, col):
1007 | return self._str_method(str.isalpha, col)
1008 |
1009 | def isdecimal(self, col):
1010 | return self._str_method(str.isdecimal, col)
1011 |
1012 | def islower(self, col):
1013 | return self._str_method(str.islower, col)
1014 |
1015 | def isnumeric(self, col):
1016 | return self._str_method(str.isnumeric, col)
1017 |
1018 | def isspace(self, col):
1019 | return self._str_method(str.isspace, col)
1020 |
1021 | def istitle(self, col):
1022 | return self._str_method(str.istitle, col)
1023 |
1024 | def isupper(self, col):
1025 | return self._str_method(str.isupper, col)
1026 |
1027 | def lstrip(self, col, chars):
1028 | return self._str_method(str.lstrip, col, chars)
1029 |
1030 | def rstrip(self, col, chars):
1031 | return self._str_method(str.rstrip, col, chars)
1032 |
1033 | def strip(self, col, chars):
1034 | return self._str_method(str.strip, col, chars)
1035 |
1036 | def replace(self, col, old, new, count=None):
1037 | if count is None:
1038 | count = -1
1039 | return self._str_method(str.replace, col, old, new, count)
1040 |
1041 | def swapcase(self, col):
1042 | return self._str_method(str.swapcase, col)
1043 |
1044 | def title(self, col):
1045 | return self._str_method(str.title, col)
1046 |
1047 | def lower(self, col):
1048 | return self._str_method(str.lower, col)
1049 |
1050 | def upper(self, col):
1051 | return self._str_method(str.upper, col)
1052 |
1053 | def zfill(self, col, width):
1054 | return self._str_method(str.zfill, col, width)
1055 |
1056 | def encode(self, col, encoding='utf-8', errors='strict'):
1057 | return self._str_method(str.encode, col, encoding, errors)
1058 |
1059 | def _str_method(self, method, col, *args):
1060 | old_values = self._df._data[col]
1061 | if old_values.dtype.kind != 'O':
1062 | raise TypeError('The `str` accessor only works with string columns')
1063 | new_values = []
1064 | for val in old_values:
1065 | if val is None:
1066 | new_values.append(val)
1067 | else:
1068 | new_val = method(val, *args)
1069 | new_values.append(new_val)
1070 | arr = np.array(new_values)
1071 | return DataFrame({col: arr})
1072 |
1073 |
1074 | def read_csv(fn):
1075 | """
1076 | Read in a comma-separated value file as a DataFrame
1077 |
1078 | Parameters
1079 | ----------
1080 | fn: string of file location
1081 |
1082 | Returns
1083 | -------
1084 | A DataFrame
1085 | """
1086 | from collections import defaultdict
1087 | values = defaultdict(list)
1088 | with open(fn) as f:
1089 | header = f.readline()
1090 | column_names = header.strip('\n').split(',')
1091 | for line in f:
1092 | vals = line.strip('\n').split(',')
1093 | for val, name in zip(vals, column_names):
1094 | values[name].append(val)
1095 | new_data = {}
1096 | for col, vals in values.items():
1097 | try:
1098 | new_data[col] = np.array(vals, dtype='int')
1099 | except ValueError:
1100 | try:
1101 | new_data[col] = np.array(vals, dtype='float')
1102 | except ValueError:
1103 | new_data[col] = np.array(vals, dtype='O')
1104 | return DataFrame(new_data)
1105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Build a Data Analysis Library from Scratch
2 |
3 | This repository contains a detailed project that teaches you how to build your own Python data analysis library, pandas_cub, from scratch. The end result will be a fully-functioning library similar to pandas.
4 |
5 | ## Video Course
6 |
7 | A detailed video course is available [on Dunder Data][22] that walks you through the entire project.
8 |
9 | ## Target Student
10 |
11 | This project is targeted towards those who understand the fundamentals
12 | of Python and would like to immerse themselves into a larger, highly structured project that covers some advanced topics. It also touches upon a few crucial areas of software development.
13 |
14 | ## Pre-Requisites
15 |
16 | This is not a project suitable for beginning Python users. At a minimum you will need to have a solid understanding of the fundamentals such as:
17 |
18 | * Basic types and common data structures (lists, tuples, sets, and dictionaries)
19 | * Control flow with if/else statements and for loops (especially when iterating through lists or dictionaries)
20 | * Raising and handling exceptions
21 | * You will need to have covered the basics of classes and object-oriented programming. If you have never defined a class before, I strongly recommend going through an introductory tutorial on them first. [This one][15] from Corey Shafer is good.
22 |
23 | In addition to those Python basics, the main workhorse is the numpy library. The project will be difficult to complete without prior exposure to numpy. This [quickstart guide][16] might be beneficial for those needing to catch up quickly.
24 |
25 | We will not be using the pandas library within our code, but will be implementing many of the same method names with similar parameters and functionality. It will be very beneficial to have some exposure to pandas before beginning.
26 |
27 | ## Objectives
28 |
29 | Most data scientists who use Python rely on pandas. In this assignment, we will build pandas cub, a library that implements many of the most common and useful methods found in pandas. Pandas Cub will:
30 |
31 | * Have a DataFrame class with data stored in numpy arrays
32 | * Select subsets of data with the brackets operator
33 | * Use special methods defined in the Python data model
34 | * Have a nicely formatted display of the DataFrame in the notebook
35 | * Implement aggregation methods - sum, min, max, mean, median, etc...
36 | * Implement non-aggregation methods such as isna, unique, rename, drop
37 | * Group by one or two columns
38 | * Have methods specific to string columns
39 | * Read in data from a comma-separated value file
40 |
41 | In addition to these items specific to data analysis, you will also learn about:
42 |
43 | * Creating a development environment
44 | * Test-Driven Development
45 |
46 | ## Setting up the Development Environment
47 |
48 | I recommend creating a new environment using the conda package manager. If you do not have conda, you can [download it here][0] along with the entire Anaconda distribution. Choose Python 3. When beginning development on a new library, it's a good idea to use a completely separate environment to write your code.
49 |
50 | ### Create the environment with the `environment.yml` file
51 |
52 | Conda allows you to automate the environment creation with an `environment.yml` file. The contents of the file are minimal and are displayed below.
53 |
54 | ```yml
55 | name: pandas_cub
56 | dependencies:
57 | - python=3.6
58 | - pandas
59 | - jupyter
60 | - pytest
61 | ```
62 |
63 | This file will be used to create a new environment named `pandas_cub`. It will install Python 3.6 in a completely separate directory in your file system along with pandas, jupyter, and pytest. There will actually be many more packages installed as those libraries have dependencies of their own. Visit [this page][2] for more information on conda environments.
64 |
65 | ### Command to create new environment
66 |
67 | In the top level directory of this repository, where the `environment.yml` file is located, run the following from your command line.
68 |
69 | `conda env create -f environment.yml`
70 |
71 | The above command will take some time to complete. Once it completes, the environment will be created.
72 |
73 | ### List the environments
74 |
75 | Run the command `conda env list` to show all the environments you have. There will be a `*` next to the active environment, which will likely be `base`, the default environment that everyone starts in.
76 |
77 | ### Activate the pandas_cub environment
78 |
79 | Creating the environment does not mean it is active. You must activate in order to use it. Use the following command to activate it.
80 |
81 | `conda activate pandas_cub`
82 |
83 | You should see `pandas_cub` in parentheses preceding your command prompt. You can run the command `conda env list` to confirm that the `*` has moved to `pandas_cub`.
84 |
85 | ### Deactivate environment
86 |
87 | You should only use the `pandas_cub` environment to develop this library. When you are done with this session, run the command `conda deactivate` to return to your default conda environment.
88 |
89 | ## Test-Driven Development with pytest
90 |
91 | The completion of each part of this project is predicated upon passing the
92 | tests written in the `test_dataframe.py` module inside the `tests` folder.
93 |
94 | We will rely upon the [pytest library][1] to test our code. We installed it along with a command line tool with the same name during our environment creation.
95 |
96 | [Test-Driven development][3] is a popular approach for developing software. It involves writing tests first and then writing code that passes the tests.
97 |
98 | ### Testing
99 |
100 | All the tests have already been written and are located in the `test_dataframe.py` module found in the `tests` directory. There are about 100 tests that you will need to pass to complete the project. To run all the tests in this file run the following on the command line.
101 |
102 | `$ pytest tests/test_dataframe.py`
103 |
104 | If you run this command right now, all the tests will fail. You should see a line of red capital 'F's. As you complete the steps in the project, you will start passing the tests. There are about 100 total tests. Once all the tests are passed, the project will be complete.
105 |
106 | ### Automated test discovery
107 |
108 | The pytest library has [rules for automated test discovery][4]. It isn't necessary to supply the path to the test module if your directories and module names follow those rules. You can simply run `pytest` to run all the tests in this library.
109 |
110 | ### Running specific tests
111 |
112 | If you open up the `test_dataframe.py` file, you will see the tests grouped under different classes. Each method of the classes represents exactly one test. To run all the tests within a single class, append two colons followed by the class name. The following is a concrete example:
113 |
114 | `$ pytest tests/test_dataframe.py::TestDataFrameCreation`
115 |
116 | It is possible to run just a single test by appending two more colons followed by the method name. Another concrete example follows:
117 |
118 | `$ pytest tests/test_dataframe.py::TestDataFrameCreation::test_input_types`
119 |
120 | ## Installing an IPython Kernel for Jupyter
121 |
122 | Although we have set up our development environment to work on the command line, we need to make a few more steps to hook it up with Jupyter Notebooks correctly.
123 |
124 | This is important, because Jupyter Notebooks are good for manually testing code as you will see below.
125 |
126 | ### Launch a Jupyter Notebook
127 |
128 | Within the `pandas_cub` environment, launch a Jupyter Notebook with the command `jupyter notebook`. When the home page finishes loading in your browser open up the `Test Notebook.ipynb` notebook.
129 |
130 | ### Changing the environment within Jupyter
131 |
132 | Although we launched our Jupyter Notebook within the `pandas_cub` environment, our code may not be executed within the `pandas_cub` environment at first. If that sounds bizarre and non-intuitive then you have reached the same conclusion as me. It is possible to run any python executable from a Jupyter Notebook regardless of the environment that it was launched from.
133 |
134 | If you run the first cell of the notebook (shown below) you can verify the location in your file system where Python is getting executed.
135 |
136 | **One of two possibilities can happen**
137 |
138 | If the value outputted from `sys.executable` is in the pandas_cub environment, it will have a path that ends like this:
139 |
140 | `anaconda3/envs/pandas_cub/bin/python`
141 |
142 | If this is your output, you can skip the rest of this step.
143 |
144 | If the value outputted from `sys.executable` ends with the following:
145 |
146 | `anaconda3/bin/python`
147 |
148 | then you are actually not executing python from the `pandas_cub` environment. You need to complete the rest of the step.
149 |
150 | ![][17]
151 |
152 | Exit out of Jupyter and return to the command line. We need to create a new [Kernel][18], a program that "runs and introspects the user’s code"
153 |
154 | Thankfully there is a command we can run with the `ipykernel` package to automatically create a new kernel. The `ipykernel` package should get installed during environment creation.
155 |
156 | The following command creates the Kernel. Make sure you have activated the `pandas_cub` environment first. You can read more about this command [in the documentation][19].
157 |
158 | ```bash
159 | python -m ipykernel install --user --name pandas_cub --display-name "Python (pandas_cub)"
160 | ```
161 |
162 | You may verify that the `pandas_cub` Kernel was created with the following command:
163 |
164 | ```bash
165 | jupyter kernelspec list
166 | ```
167 |
168 | ### Launch Jupyter Again
169 |
170 | Launch Jupyter again and open up the `Test Notebook.ipynb` notebook. You will still NOT be in the `pandas_cub` environment. You need to navigate inside the 'Kernel' menu above and into 'Change kernel'. Finally, you can select the 'Python (pandas_cub)' Kernel which will place you in the right environment. The kernel will restart once you choose this option.
171 |
172 | ![][20]
173 |
174 | Run the first cell of the notebook again and you should see that the Python executable is coming from the `pandas_cub` environment directory.
175 |
176 | ![][21]
177 |
178 | You don't have to do this procedure again or this notebook. From now on, it will open up using the `pandas_cub` Kernel that was created. You can of course change the Kernel again but this is its new default. To verify this, shutdown the notebook and restart it.
179 |
180 | If you start a new notebook, you will have the option to decide which Kernel you would like to run it with.
181 |
182 | ## Inspecting the `__init__.py` File
183 |
184 | You will be editing a single file for this project - the `__init__.py` file
185 | found in the `pandas_cub` directory. It contains skeleton code for the entire project. You won't be defining your own classes or methods, but you will be filling out the method bodies.
186 |
187 | Open up this file now. You will see many incomplete methods that have the keyword `pass` as their last line. These are the methods that you will be editing. A few methods are complete and won't need editing.
188 |
189 | ### Docstrings
190 |
191 | You'll notice that all the methods have triple quoted strings directly beneath them. These strings are the documentation or 'docstrings'. All docstrings begin with a short summary of what the method does. A Parameters section follows thats lists each parameter, its type, and a description of how its used. The docstrings end with a Returns section that informs the user of what type of object is returned. It's important to read them as they contain information on how to complete the methods.
192 |
193 | There are many ways you can write docstrings, but these follow the [numpy docstring guide][7]. There are many other sections you may add to them as well.
194 |
195 | ## Importing pandas_cub
196 |
197 | Return back to the Jupyter notebook, which should already be open. This notebook is at the same level as the inner `pandas_cub` directory. This means that we can import `pandas_cub` directly into our namespace without changing directories. Technically, `pandas_cub` is a Python **package**, which is a directory containing a `__init__.py` file. It is this initialization file that gets run when we write `import pandas_cub as pdc`.
198 |
199 | ## Manually Test in a Jupyter Notebook
200 |
201 | During development, it's good to have a place to manually experiment with your new code so you can see it in action. We will be using the Jupyter Notebook to quickly see how our DataFrame is changing.
202 |
203 | ### Autoreloading
204 |
205 | The second cell loads a notebook magic extension which automatically reloads code from files that have changed. Normally, we would have to restart the kernel if we made changes to our code to see it reflect its current state. This magic command saves us from doing this.
206 |
207 | ### Imports
208 |
209 | Along with `pandas_cub` `pandas_cub_final` is also imported so you can see how the completed object is supposed to behave.
210 |
211 | We import the `pandas` library so that you can compare and contrast its functionality.
212 |
213 | ### A test DataFrame
214 |
215 | A simple test DataFrame is created for `pandas_cub`, `pandas_cub_final`, and `pandas`. The output for all three DataFrames are produced in the notebook. There currently is no nice visual representation for `pandas_cub` DataFrames.
216 |
217 | ## Starting Pandas Cub
218 |
219 | Keep the `__init__.py` file open at all times. This is the only file that you will be editing. Read and complete each numbered step below. Edit the method indicated in each step and then run the test. Once you pass that test, move on to the next step.
220 |
221 | ### The answer is in pandas_cub_final
222 |
223 | The `pandas_cub_final` directory contains the completed `__init__.py` file with the code that passes all the tests. Only look at this file after you have attempted to complete each step on your own.
224 |
225 | ### 1. Check DataFrame constructor input types
226 |
227 | Our DataFrame class is constructed with a single parameter, `data`. Python will call the special `__init__` method when first constructing our DataFrame. This method has already been completed for you and you will not need to edit it. Within the `__init__` method, several more methods are called that check to see if the user has passed it valid data. You will be editing these methods during the next few steps.
228 |
229 | In this step, you will only be editing the `_check_input_types` method. This is the first method called within the `__init__` method. It will ensure that our users have passed us a valid `data` parameter.
230 |
231 | We are going to force our users to set `data` as a dictionary that has strings as the keys and one-dimensional numpy arrays as the values. The keys will eventually become the column names and the arrays will be the values of those columns.
232 |
233 | Specifically, `_check_input_types` must do the following:
234 |
235 | * raise a `TypeError` if `data` is not a dictionary
236 | * raise a `TypeError` if the keys of `data` are not strings
237 | * raise a `TypeError` if the values of `data` are not numpy arrays
238 | * raise a `ValueError` if the values of `data` are not 1-dimensional
239 |
240 | Edit this method now. Use the `isinstance` function to help you determine the type of an object.
241 |
242 | Run the following command to test this step. Once you have passed this test move on to the next step.
243 |
244 | `$ pytest tests/test_dataframe.py::TestDataFrameCreation::test_input_types`
245 |
246 | ### 2. Check array lengths
247 |
248 | We are now guaranteed that `data` is a dictionary of strings mapped to one-dimensional arrays. Each column of data in our DataFrame must have the same number of elements. In this step, you must ensure that this is the case. Edit the `_check_array_lengths` method and raise a `ValueError` if any of the arrays differ in length.
249 |
250 | Run the following test:
251 |
252 | `$ pytest tests/test_dataframe.py::TestDataFrameCreation::test_array_length`
253 |
254 | ### 3. Convert unicode arrays to object
255 |
256 | Whenever you create a numpy array of Python strings, it will default the data type of that array to unicode. Take a look at the following simple numpy array created from strings. Its data type, found in the `dtype` attribute is shown to be 'U' plus the length of the longest string.
257 |
258 | ```python
259 | >>> a = np.array(['cat', 'dog', 'snake'])
260 | >>> a.dtype
261 | dtype('>> a.dtype.kind
270 | 'U'
271 | ```
272 |
273 | Pass the `astype` array method the correct kind character to change its type.
274 |
275 | Edit the `_convert_unicode_to_object` method and fill the dictionary `new_data` with the converted arrays. The result of this method will be returned and assigned as the `_data` instance variable.
276 |
277 | Run `test_unicode_to_object` to test.
278 |
279 | ### A note on names that begin with a single underscore
280 |
281 | So far we have seen a few examples of attribute and method names within our DataFrame class that begin with a single underscore. These names are intended to be 'private' and not directly accessed by our users. This doesn't prevent our users from accessing these names as nothing is technically private in Python, but it is common convention and discussed in [this section of the PEP8 style guide][9].
282 |
283 | Most IDEs will not show these private methods as choices to the users which is a good thing. They are not at all meant to be accessed by them.
284 |
285 | ### 4. Find the number of rows in the DataFrame with the `len` function
286 |
287 | The number of rows are returned when passing a pandas DataFrame to the builtin `len` function. We will make pandas_cub behave the same exact way.
288 |
289 | To do so we need to implement the special method `__len__`. This is what Python calls whenever an object is passed to the `len` function.
290 |
291 | Edit the `__len__` method and have it return the number of rows. Test with `test_len`.
292 |
293 | ### Special Methods
294 |
295 | Step 4 introduced us to the `__len__` 'special method'. Python has over 100 special methods that allow you to define how your class behaves when it interacts with a builtin function or operator. In the above example, if `df` is a DataFrame and a user calls `len(df)` then internally the `__len__` method will be called. All special methods begin and end with two underscores.
296 |
297 | Let's see a few more examples:
298 |
299 | * `df + 5` calls the `__add__` special method
300 | * `df > 5` calls the `__lt__` special method
301 | * `-df` calls the `__neg__` special method
302 | * `round(df)` calls the `__round__` special method
303 |
304 | We've actually already seen the special method `__init__` which is used to initialize an object and called when a user calls `DataFrame(data)`.
305 |
306 | The [Python documentation][10] has good (though complex) coverage of all the special methods. We will be implementing many more special methods. I strongly recommend to reference the documentation to learn more.
307 |
308 | ### 5. Return columns as a list
309 |
310 | In this step you will make `df.columns` return a list of the column names. Notice that `df.columns` is not a method here. There will be no parentheses that follow it.
311 |
312 | Looking at the source code, you will see that `columns` appears to be defined as if it is a method. But, directly above it is the property decorator. The `property` decorator will make `df.columns` work just like a method.
313 |
314 | Currently the keys in our `_data` dictionary refer to the columns in our DataFrame. Edit the `columns` 'method' (really a property) to return a list of the columns in order. Since we are working with Python 3.6, the dictionary keys are internally ordered. Take advantage of this. Validate with the `test_columns` test.
315 |
316 | ### The property decorator
317 |
318 | There is quite a bit more to the property decorator, including how its used to set attributes as is done in the next step. [This Stack Overflow question][11] contains a good examples that will explain more.
319 |
320 | ### 6. Set new column names
321 |
322 | In this step, we will be assigning all new columns to our DataFrame by setting the columns property equal to a list. A concrete example below shows how you would set new columns for a 3-column DataFrame.
323 |
324 | ```python
325 | df.columns = ['state', 'age', 'fruit']
326 | ```
327 |
328 | There are three parts to properties in Python; the getter, setter, and deleter. In the previous step, we defined the getter. In this step we will define the setter with the `columns.setter` decorator. The value on the right hand side of the assignment statement is passed to the method decorated by `columns.setter`. Edit this method and complete the following tasks:
329 |
330 | * Raise a `TypeError` if the object used to set new columns is not a list
331 | * Raise a `ValueError` if the number of column names in the list does not match the current DataFrame
332 | * Raise a `TypeError` if any of the columns are not strings
333 | * Raise a `ValueError` if any of the column names are duplicated in the list
334 | * Reassign the `_data` variable so that all the keys have been updated
335 |
336 | Test with `test_set_columns`.
337 |
338 | ### 7. The `shape` property
339 |
340 | The `shape` property will return a two-item tuple of the number of rows and columns. The property decorator is used again here so that `df.shape` can execute code like a method. We could just make it a normal method and invoke it with `df.shape()` but we are following pandas lead and keeping `shape` as a property.
341 |
342 | Test with `test_shape`.
343 |
344 | ### 8. Visual HTML representation in the notebook with the `_repr_html_` method
345 |
346 | Currently we have no representation of our DataFrame. If you try and output your DataFrame, you'll just get its location in memory and it will look something like this:
347 |
348 | ```python
349 | >>> df
350 |
351 | ```
352 |
353 | The `_repr_html_` method is made available to developers by iPython so that your objects can have nicely formatted HTML displays within Jupyter Notebooks. Read more on this method [here in the iPython documentation][12] along with other similar methods for different representations.
354 |
355 | This method must return a string of html. This method is fairly complex and you must know some basic html to complete it. I recommend copying and pasting the implementation from pandas_cub_final instead of doing it yourself.
356 |
357 | If you do know HTML and are seeking a greater challenger use the docstrings to give you an idea of how the HTML may be formatted. There are no tests for this method.
358 |
359 | ### 9. The `values` property
360 |
361 | In pandas, `values` is a property that returns a single array of all the columns of data. Our DataFrame will do the same. Edit the `values` property and concatenate all the column arrays into a single two-dimensional numpy array. Return this array. The numpy `column_stack` function can be helpful here.
362 |
363 | Test with `test_values`.
364 |
365 | ### Hint when returning a DataFrame from a property/method
366 |
367 | Many of the next steps require you to return a DataFrame as the result of the property/method. To do so, you will use the DataFrame constructor like this.
368 |
369 | ```python
370 | return DataFrame(new_data)
371 | ```
372 |
373 | where `new_data` is a dictionary mapping the column names to a one-dimensional numpy array. It is your job to create the `new_data` dictionary correctly.
374 |
375 | ### 10. The `dtypes` property
376 |
377 | The `dtypes` property will return a two-column DataFrame with the column names in the first column and their data type as a string in the other. Use 'Column Name' and 'Data Type' as column names.
378 |
379 | Use the `DTYPE_NAME` dictionary to convert from array `kind` to the string name of the data type. Test with `test_dtypes`.
380 |
381 | ### 11. Select a single column with the brackets
382 |
383 | In pandas, you can select a single column with `df['colname']`. Our DataFrame will do the same. To make an object work with the brackets, you must implement the `__getitem__` special method. See the [official documentation][13] for more.
384 |
385 | This special method is always passed a single parameter, the value within the brackets. We use `item` as the parameter name.
386 |
387 | In this step, use `isinstance` to check whether `item` is a string. If it is, return a one column DataFrame of that column. You will need to use the `DataFrame` constructor to return a DataFrame.
388 |
389 | These tests are under the `TestSelection` class. Run the `test_one_column` test.
390 |
391 | ### 12. Select multiple columns with a list
392 |
393 | Our DataFrame will also be able to select multiple columns if given a list within the brackets. For example, `df[['colname1', 'colname2']]` will return a two column DataFrame.
394 |
395 | Continue editing the `__getitem__` method. If `item` is a list, return a DataFrame of just those columns. Run `test_multiple_columns` to test.
396 |
397 | ### 13. Boolean Selection with a DataFrame
398 |
399 | In pandas, you can filter for specific rows of a DataFrame by passing in a boolean Series/array to the brackets. For instance, the following will select only the rows such that `a` is greater than 10.
400 |
401 | ```python
402 | >>> s = df['a'] > 10
403 | >>> df[s]
404 | ```
405 |
406 | This is called boolean selection. We will make our DataFrame work similarly. Edit the `__getitem__` method and check whether `item` is a DataFrame. If it is then do the following:
407 |
408 | * If it is more than one column, raise a `ValueError`
409 | * Extract the underlying array from the single column
410 | * If the underlying array kind is not boolean ('b') raise a `ValueError`
411 | * Use the boolean array to return a new DataFrame with just the rows where the boolean array is `True` along with all the columns.
412 |
413 | Run `test_simple_boolean` to test
414 |
415 | ### (Optional) Simultaneous selection of rows and column
416 |
417 | Steps 14-18 are optional and fairly difficult. The outcome of these steps is to simultaneously select both rows and columns in the DataFrame. The syntax uses the brackets operator like the previous three steps and looks like this:
418 |
419 | ```python
420 | df[rs, cs]
421 | ```
422 |
423 | where `rs` is the row selection and `cs` is the column selection.
424 |
425 | ### 14. (Optional) Check for simultaneous selection of rows and columns
426 |
427 | When you pass the brackets operator a sequence of comma separated values with `df[rs, cs]`, Python passes the `__getitem__` special method a tuple of all the values.
428 |
429 | To get started coding, within the `__getitem__` special method check whether `item` is a tuple instance. If is not, raise a `TypeError` and inform the user that they need to pass in either a string (step 11), a list of strings (step 12), a one column boolean DataFrame (step 13) or both a row and column selection (step 14).
430 |
431 | If `item` is a tuple, return the result of a call to the `_getitem_tuple` method.
432 |
433 | **Edit the `_getitem_tuple` method from now through step 18.**
434 |
435 | Within the `_getitem_tuple` method, raise a `ValueError` if it is not exactly two items in length.
436 |
437 | Run `test_simultaneous_tuple` to test.
438 |
439 | ### 15. (Optional) Select a single cell of data
440 |
441 | In this step, we will select a single cell of data with `df[rs, cs]`. We will assume `rs` is an integer and `cs` is either an integer or a string.
442 |
443 | To get started, assign the first element of `item` to the variable `row_selection` and the second element of `item` to `col_selection`. From step 14, we know that `item` must be a two-item tuple.
444 |
445 | If `row_selection` is an integer, reassign it as a one-element list of that integer.
446 |
447 | Check whether `col_selection` is an integer. If it is, reassign to a one-element list of the string column name it represents.
448 |
449 | If `col_selection` is a string, assign it to a one-element list of that string.
450 |
451 | Now both `row_selection` and `col_selection` are lists. You will return a single-row, single-column DataFrame. This is different than pandas, which just returns a scalar value.
452 |
453 | Write a for loop to iterate through each column in the `col_selection` list to create the `new_data` dictionary. Make sure to select just the row that is needed.
454 |
455 | This for-loop will be used for the steps through 18 to return the desired DataFrame.
456 |
457 | Run `test_single_element` to test.
458 |
459 | ### 16. (Optional) Simultaneously select rows as booleans, lists, or slices
460 |
461 | In this step, we will again be selecting rows and columns simultaneously with `df[rs, cs]`. We will allow `rs` to be either a single-column boolean DataFrame, a list of integers, or a slice. For now, `cs` will remain either an integer or a string. The following selections will be possible after this step.
462 |
463 | ```python
464 | df[df['a'] < 10, 'b']
465 | df[[2, 4, 1], 'e']
466 | df[2:5, 3]
467 | ```
468 |
469 | If `row_selection` is a DataFrame, raise a `ValueError` if it is not one column. Reassign `row_selection` to the values (numpy array) of its column. Raise a `TypeError` if it is not a boolean data type.
470 |
471 | If `row_selection` is not a list or a slice raise a `TypeError` and inform the user that the row selection must be either an integer, list, slice, or DataFrame. You will not need to reassign `row_selection` for this case as it will select properly from a numpy array.
472 |
473 | Your for-loop from step 15 should return the DataFrame.
474 |
475 | Run `test_all_row_selections` to test.
476 |
477 | ### 17. (Optional) Simultaneous selection with multiple columns as a list
478 |
479 | The `row_selection` variable is now fully implemented. It can be either an integer, list of integers, a slice, or a one-column boolean DataFrame.
480 |
481 | As of now, the `col_selection` can only be an integer or a string. In this step, we will handle the case when it is a list.
482 |
483 | If `col_selection` is a list, create an empty list named `new_col_selection`. Iterate through each element of `col_selection` and check if it is an integer. If it is, append the string column name to `new_col_selection`. If not, assume it is a string and append it as it is to `new_col_selection`.
484 |
485 | `new_col_selection` will now be a list of string column names. Reassign `col_selection` to it.
486 |
487 | Again, your for-loop from step 15 will return the DataFrame.
488 |
489 | Run `test_list_columns` to test.
490 |
491 | ### 18. (Optional) Simultaneous selection with column slices
492 |
493 | In this step, we will allow our columns to be sliced with either strings or integers. The following selections will be acceptable.
494 |
495 | ```python
496 | df[rs, :3]
497 | df[rs, 1:10:2]
498 | df[rs, 'a':'f':2]
499 | ```
500 |
501 | Where `rs` is any of the previously acceptable row selections.
502 |
503 | Check if `col_selection` is a slice. Slice objects have `start`, `stop`, and `step` attributes. Define new variables with the same name to hold those attributes of the slice object.
504 |
505 | If `col_selection` is not a slice raise a `TypeError` informing the user that the column selection must be an integer, string, list, or slice.
506 |
507 | If `start` is a string, reassign it to its integer index amongst the columns.
508 |
509 | If `stop` is a string, reassign it to its integer index amongst the columns **plus 1**. We add one here so that we include the last column.
510 |
511 | `start`, `stop`, and `step` should now be integers. Use them to reassign `col_selection` to a list of all the column names that are to be selected. You'll use slice notation to do this.
512 |
513 | The for-loop from 15 will still work to return the desired DataFrame.
514 |
515 | Run `test_col_slice` to test.
516 |
517 | ### 19. Tab Completion for column names
518 |
519 | It is possible to get help completing column names when doing single-column selections. For instance, let's say we had a column name called 'state' and began making a column selection with `df['s]`. If we press tab right here iPython can show us a dropdown list of all the column names beginning with 's'.
520 |
521 | We do this by returning the list of possible values we want to see from the `_ipython_key_completions_` method. Complete that method now.
522 |
523 | Run `test_tab_complete` to test.
524 |
525 | ### 20. Create a new column or overwrite an old column
526 |
527 | We will now have our DataFrame create a single new column or overwrite an existing one. Pandas allows for setting multiple columns at once, and even setting rows and columns simultaneously. Doing such is fairly complex and we will not implement those cases and instead focus on just single-column setting.
528 |
529 | Python allows setting via the brackets with the `__setitem__` special method. It receives two values when called, the `key` and the `value`. For instance, if we set a new column like this:
530 |
531 | ```python
532 | df['new col'] = np.array([10, 4, 99])
533 | ```
534 |
535 | the `key` would be 'new col' and the `value` would be the numpy array.
536 |
537 | If the `key` is not a string, raise a `NotImplementedError` stating that the DataFrame can only set a single column.
538 |
539 | If `value` is a numpy array, raise a `ValueError` if it is not 1D. Raise a different `ValueError` if the length is different than the calling DataFrame.
540 |
541 | If `value` is a DataFrame, raise a `ValueError` if it is not a single column. Raise a different `ValueError` if the length is different than the calling DataFrame. Reassign `value` to the underlying numpy array of the column.
542 |
543 | If `value` is a single integer, string, float, or boolean, use the numpy `repeat` function to reassign `value` to be an array the same length as the DataFrame with all values the same. For instance, the following should work.
544 |
545 | ```python
546 | >>> df['new col'] = 85
547 | ```
548 |
549 | Raise a `TypeError` if `value` is not one of the above types.
550 |
551 | After completing the above, `value` will be a one-dimensional array. If it's data type `kind` is the string 'U', change its type to object.
552 |
553 | Finally, assign a new column by modifying the `_data` dictionary.
554 |
555 | Run `test_new_column` to test.
556 |
557 | ### 21. `head` and `tail` methods
558 |
559 | The `head` and `tail` methods each accept a single integer parameter `n` which is defaulted to 5. Have them return the first/last n rows.
560 |
561 | Run `test_head_tail` to complete this.
562 |
563 | ### 22. Generic aggregation methods
564 |
565 | We will now implement several methods that perform an aggregation. These methods all return a single value for each column. The following aggregation methods are defined.
566 |
567 | * min
568 | * max
569 | * mean
570 | * median
571 | * sum
572 | * var
573 | * std
574 | * all
575 | * any
576 | * argmax - index of the maximum
577 | * argmin - index of the minimum
578 |
579 | We will only be performing these aggregations column-wise and not row-wise. Pandas enables users to perform both row and column aggregations.
580 |
581 | If you look at our source code, you will see all of the aggregation methods already defined. You will not have to modify any of these methods individually. Instead, they all call the underlying `_agg` method passing it the numpy function.
582 |
583 | Complete the generic method `_agg` that accepts an aggregation function.
584 |
585 | Iterate through each column of your DataFrame and pass the underlying array to the aggregation function. Return a new DataFrame with the same number of columns, but with just a single row, the value of the aggregation.
586 |
587 | String columns with missing values raise a `TypeError`. Except this error and don't return columns where the aggregation cannot be found.
588 |
589 | Defining just the `_agg` method will make all the other aggregation methods work.
590 |
591 | All the aggregation methods have their own tests in a separate class named `TestAggregation`. They are all named similarly with 'test_' preceding the name of the aggregation. Run all the tests at once.
592 |
593 | ### 23. `isna` method
594 |
595 | The `isna` method will return a DataFrame the same shape as the original but with boolean values for every single value. Each value will be tested whether it is missing or not. Use `np.isnan` except in the case for strings which you can use a vectorized equality expression to `None`.
596 |
597 | Test with `test_isna` found in the `TestOtherMethods` class.
598 |
599 | ### 24. `count` method
600 |
601 | The `count` method returns a single-row DataFrame with the number of non-missing values for each column. You will want to use the result of `isna`.
602 |
603 | Test with `test_count`
604 |
605 | ### 25. `unique` method
606 |
607 | This method will return the unique values for each column in the DataFrame. Specifically, it will return a list of one-column DataFrames of unique values in each column. If there is a single column, just return the DataFrame.
608 |
609 | The reason we use a list of DataFrames is that each column may contain a different number of unique values. Use the `unique` numpy function.
610 |
611 | Test with `test_unique`
612 |
613 | ### 26. `nunique` method
614 |
615 | Return a single-row DataFrame with the number of unique values for each column.
616 |
617 | Test with `test_nunique`
618 |
619 | ### 27. `value_counts` method
620 |
621 | Return a list of DataFrames, unless there is just one column and then just return a single DataFrame. Each DataFrame will be two columns. The first column name will be the name of the original column. The second column name will be 'count'. The first column will contain the unique values in the original DataFrame column. The 'count' column will hold the frequency of each of those unique values.
622 |
623 | Use the numpy `unique` function with `return_counts` set to `True`. Return the DataFrames with sorted counts from greatest to least. Use the numpy `argsort` to help with this.
624 |
625 | Use the `test_value_counts` test within the `TestGrouping` class.
626 |
627 | ### 28. Normalize options for `value_counts`
628 |
629 | We will modify the `value_counts` method to return relative frequencies. The `value_counts` method also accepts a boolean parameter `normalize` that by default is set to `False`. If it is `True`, then return the relative frequencies of each value instead.
630 |
631 | Test with `test_value_counts_normalize`
632 |
633 | ### 29. `rename` method
634 |
635 | The `rename` method renames one or more column names. Accept a dictionary of old column names mapped to new column names. Return a DataFrame. Raise a `TypeError` if `columns` is not a dictionary.
636 |
637 | Test with`test_rename` within the `TestOtherMethods` class
638 |
639 | ### 30. `drop` method
640 |
641 | Accept a single string or a list of column names as strings. Return a DataFrame without those columns. Raise a `TypeError` if a string or list is not provided.
642 |
643 | Test with `test_drop`
644 |
645 | ### 31. Non-aggregation methods
646 |
647 | There are several non-aggregation methods that function similarly. All of the following non-aggregation methods return a DataFrame that is the same shape as the origin.
648 |
649 | * `abs`
650 | * `cummin`
651 | * `cummax`
652 | * `cumsum`
653 | * `clip`
654 | * `round`
655 | * `copy`
656 |
657 | All of the above methods will be implemented with the generic `_non_agg` method. This method is sent the numpy function name of the non-aggregating method.
658 |
659 | Pass only the boolean, integer, and float columns to this non-aggregating numpy function.
660 |
661 | Keep the string columns (only other data type) in your returned DataFrame. Use the `copy` array method to make an independent copy of them.
662 |
663 | Notice that some of these non-aggregating methods have extra keyword arguments. These are passed to `_non_agg` and collected with `**kwargs`. Make sure to pass them to the numpy function as well.
664 |
665 | There is a different test for each method in the `TestNonAgg` class.
666 |
667 | #### Update after videos
668 |
669 | If you are watching my videos for the course, I updated the pandas_cub_final init file to contain a better solution. The `round` method should ignore boolean columns. The original solution applied had each non-aggregation method work on boolean, integer, and float columns.
670 |
671 | ### 32. `diff` method
672 |
673 | The `diff` method accepts a single parameter `n` and takes the difference between the current row and the `n` previous row. For instance, if a column has the values [5, 10, 2] and `n=1`, the `diff` method would return [NaN, 5, -8]. The first value is missing because there is no value preceding it.
674 |
675 | The `diff` method is a non-aggregating method as well, but there is no direct numpy function that computes it. Instead, we will define a function within this method that computes this difference.
676 |
677 | Complete the body of the `func` function.
678 |
679 | Allow `n` to be either a negative or positive integer. You will have to set the first or last n values to `np.nan`. If you are doing this on an integer column, you will have to convert it to a float first as integer arrays cannot contain missing values. Use `np.roll` to help shift the data in the arrays.
680 |
681 | Test with `test_diff`
682 |
683 | ### 33. `pct_change` method
684 |
685 | The `pct_change` method is nearly identical to the `diff` method. The only difference is that this method returns the percentage change between the values and not the raw difference. Again, complete the body of the `func` function.
686 |
687 | Test with `test_pct_change`
688 |
689 | ### 34. Arithmetic and Comparison Operators
690 |
691 | All the common arithmetic and comparison operators will be made available to our DataFrame. For example, `df + 5` uses the plus operator to add 5 to each element of the DataFrame. Take a look at some of the following examples:
692 |
693 | ```python
694 | df + 5
695 | df - 5
696 | df > 5
697 | df != 5
698 | 5 + df
699 | 5 < df
700 | ```
701 |
702 | All the arithmetic and comparison operators have corresponding special methods that are called whenever the operator is used. For instance `__add__` is called when the plus operator is used, and `__le__` is called whenever the less than or equal to operator is used. See [the full list][14] in the documentation.
703 |
704 | Each of these methods accepts a single parameter, which we have named `other`. All of these methods call a more generic `_oper` method which you will complete.
705 |
706 | Within the `_oper` method check if `other` is a DataFrame. Raise a `ValueError` if this DataFrame not one column. Otherwise, reassign `other` to be a 1D array of the values of its only column.
707 |
708 | If `other` is not a DataFrame do nothing and continue executing the rest of the method. We will not check directly if the types are compatible. Instead we will pass this task onto numpy. So, `df + 5` should work if all the columns in `df` are booleans, integers, or floats.
709 |
710 | Iterate through all the columns of your DataFrame and apply the operation to each array. You will need to use the `getattr` function along with the `op` string to retrieve the underlying numpy array method. For instance, `getattr(values, '__add__')` returns the method that uses the plus operator for the numpy array `values`. Return a new DataFrame with the operation applied to each column.
711 |
712 | Run all the tests in class `TestOperators`
713 |
714 | ### 35. `sort_values` method
715 |
716 | This method will sort the rows of the DataFrame by one or more columns. Allow the parameter `by` to be either a single column name as a string or a list of column names as strings. The DataFrame will be sorted by this column or columns.
717 |
718 | The second parameter, `asc`, will be a boolean controlling the direction of the sort. It is defaulted to `True` indicating that sorting will be ascending (lowest to greatest). Raise a `TypeError` if `by` is not a string or list.
719 |
720 | You will need to use numpy's `argsort` to get the order of the sort for a single column and `lexsort` to sort multiple columns.
721 |
722 | Run the following tests in the `TestMoreMethods` class.
723 |
724 | * `test_sort_values`
725 | * `test_sort_values_desc`
726 | * `test_sort_values_two`
727 | * `test_sort_values_two_desc`
728 |
729 | ### 36. `sample` method
730 |
731 | This method randomly samples the rows of the DataFrame. You can either choose an exact number to sample with `n` or a fraction with `frac`. Sample with replacement by using the boolean `replace`. The `seed` parameter will be used to set the random number seed.
732 |
733 | Raise a `ValueError` if `frac` is not positive and a `TypeError` if `n` is not an integer.
734 |
735 | You will be using numpy's random module to complete this method. Within it are the `seed` and `choice` functions. The latter function has a `replace` parameter that you will need to use. Return a new DataFrame with the new random rows.
736 |
737 | Run `test_sample` to test.
738 |
739 | ### 37. `pivot_table` method
740 |
741 | This is a complex method to implement. This method allows you to create a [pivot table][5] from your DataFrame. The following image shows the final result of calling the pivot table on a DataFrame. It summarizes the mean salary of each gender for each race.
742 |
743 | ![pt][6]
744 |
745 | A typical pivot table uses two columns as the **grouping columns** from your original DataFrame. The unique values of one of the grouping columns form a new column in the new DataFrame. In the example above, the race column had five unique values.
746 |
747 | The unique values of the other grouping column now form the columns of the new DataFrame. In the above example, there were two unique values of gender.
748 |
749 | In addition to the grouping columns is the **aggregating column**. This is typically a numeric column that will get summarized. In the above pivot table, the salary column was aggregated.
750 |
751 | The last component of a pivot table is the **aggregating function**. This determines how the aggregating columns get aggregated. Here, we used the `mean` function.
752 |
753 | The syntax used to produce the pivot table above is as follows:
754 |
755 | ```python
756 | df.pivot_table(rows='race', columns='gender', values='salary', aggfunc='mean')
757 | ```
758 |
759 | `rows` and `columns` will be assigned the grouping columns. `values` will be assigned the aggregating column and `aggfunc` will be assigned the aggregating function. All four parameters will be strings. Since `aggfunc` is a string, you will need to use the builtin `getattr` function to get the correct numpy function.
760 |
761 | There are several approaches that you can take to implement this. One approach involves using a dictionary to store the unique combinations of the grouping columns as the keys and a list to store the values of the aggregative column. You could iterate over every single row and then use a two-item tuple to hold the values of the two grouping columns. A `defaultdict` from the collections module can help make this easier. Your dictionary would look something like this after you have iterated through the data.
762 |
763 | ```python
764 | {('black', 'male'): [50000, 90000, 40000],
765 | ('black', 'female'): [100000, 40000, 30000]}
766 | ```
767 |
768 | Once you have mapped the groups to their respective values, you would need to iterate through this dictionary and apply the aggregation function to the values. Create a new dictionary for this.
769 |
770 | From here, you need to figure out how to turn this dictionary into the final DataFrame. You have all the values, you just need to create a dictionary of columns mapped to values. Use the first column as the unique values of the rows column.
771 |
772 | Other features:
773 |
774 | * Return a DataFrame that has the rows and columns sorted
775 | * You must make your pivot table work when passed just one of `rows` or `columns`. If just `rows` is passed return a two-column DataFrame with the first column containing the unique values of the rows and the second column containing the aggregations. Title the second column the same name as `aggfunc`.
776 | * If `aggfunc` is `None` and `values` is not None then raise a `ValueError`.
777 | * If `values` is `None` and `aggfunc` is not then raise a `ValueError` as there are no values to be aggregated.
778 | * If `aggfunc` and `values` are both `None` then set `aggfunc` equal to the string 'size'. This will produce a contingency table (the raw frequency of occurrence). You might need to create an empty numpy array to be a placeholder for the values.
779 |
780 | Run `test_pivot_table_rows_or_cols` and `test_pivot_table_both` in the `TestGrouping` class.
781 |
782 | ### 38. Automatically add documentation
783 |
784 | All docstrings can be retrieved programmitcally with the `__doc__` special attribute. Docstrings can also be dynamically set by assigning this same special attribute a string.
785 |
786 | This method is already completed and automatically adds documentation to the aggregation methods by setting the `__doc__` special attribute.
787 |
788 | ### 39. String-only methods with the `str` accessor
789 |
790 | Look back up at the `__init__` method. One of the last lines defines `str` as an instance variable assigned to a new instance of `StringMethods`. Pandas uses the same variable name for its DataFrames and calls it a string 'accessor'. We will also refer to it as an accessor as it gives us access to string-only methods.
791 |
792 | Scroll down below the definition of the `DataFrame` class. You will see the `StringMethods` class defined there. During initialization it stores a reference to the underlying DataFrame with `_df`.
793 |
794 | There are many string methods defined in this class. The first parameter to each string method is the name of the column you would like to apply the string method to. We will only allow our accessor to work on a single column of the DataFrame.
795 |
796 | You will only be modifying the `_str_method` which accepts the string method, the name of the column, and any extra arguments.
797 |
798 | Within `_str_method` select the underlying numpy array of the given `col`. Raise a `TypeError` if it does not have kind 'O'.
799 |
800 | Iterate over each value in the array and pass it to `method`. It will look like this: `method(val, *args)`. Return a one-column DataFrame with the new data.
801 |
802 | Test with class `TestStrings`
803 |
804 | ### 40. Reading simple CSVs
805 |
806 | It is important that our library be able to turn data in files into DataFrames. The `read_csv` function, at the very end of our module, will read in simple comma-separated value files (CSVs) and return a DataFrame.
807 |
808 | The `read_csv` function accepts a single parameter, `fn`, which is a string of the file name containing the data. Read through each line of the file. Assume the values in each line are separated by commas. Also assume the first line contains the column names.
809 |
810 | Create a dictionary to hold the data and return a new DataFrame. Use the file `employee.csv` in the `data` directory to test your function manually.
811 |
812 | Run all the tests in the `TestReadCSV` class.
813 |
814 | [0]: https://www.anaconda.com/distribution/
815 | [1]: https://docs.pytest.org/en/latest/getting-started.html
816 | [2]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html
817 | [3]: https://en.wikipedia.org/wiki/Test-driven_development
818 | [4]: https://docs.pytest.org/en/latest/goodpractices.html#conventions-for-python-test-discovery
819 | [5]: https://en.wikipedia.org/wiki/Pivot_table
820 | [6]: images/pivot.png
821 | [7]: https://numpydoc.readthedocs.io/en/latest/format.html
822 | [8]: https://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.kind.html#numpy.dtype.kind
823 | [9]: https://www.python.org/dev/peps/pep-0008/#id47
824 | [10]: https://docs.python.org/3/reference/datamodel.html#specialnames
825 | [11]: https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work
826 | [12]: https://ipython.readthedocs.io/en/stable/config/integrating.html
827 | [13]: https://docs.python.org/3/reference/datamodel.html
828 | [14]: https://docs.python.org/3/reference/datamodel.html#emulating-numeric-type
829 | [15]: https://www.youtube.com/watch?v=ZDa-Z5JzLYM
830 | [16]: https://docs.scipy.org/doc/numpy/user/quickstart.html
831 | [17]: images/exec_location.png
832 | [18]: https://jupyter-client.readthedocs.io/en/latest/kernels.html#making-kernels-for-jupyter
833 | [19]: https://ipython.readthedocs.io/en/stable/install/kernel_install.html
834 | [20]: images/change_kernel.png
835 | [21]: images/exec_location2.png
836 | [22]: https://www.dunderdata.com/offers/YpStPwfH/checkout
837 |
--------------------------------------------------------------------------------
/data/employee.csv:
--------------------------------------------------------------------------------
1 | dept,race,gender,salary
2 | Houston Police Department-HPD,White,Male,45279
3 | Houston Fire Department (HFD),White,Male,63166
4 | Houston Police Department-HPD,Black,Male,66614
5 | Public Works & Engineering-PWE,Asian,Male,71680
6 | Houston Airport System (HAS),White,Male,42390
7 | Public Works & Engineering-PWE,White,Male,107962
8 | Houston Fire Department (HFD),Hispanic,Male,52644
9 | Health & Human Services,Black,Male,180416
10 | Public Works & Engineering-PWE,Black,Male,30347
11 | Health & Human Services,Black,Male,55269
12 | Houston Police Department-HPD,Black,Male,77076
13 | Houston Police Department-HPD,White,Male,81239
14 | Houston Police Department-HPD,White,Male,66614
15 | Public Works & Engineering-PWE,White,Male,61506
16 | Houston Fire Department (HFD),Black,Male,57815
17 | Public Works & Engineering-PWE,Black,Male,43264
18 | Houston Fire Department (HFD),White,Male,55437
19 | Houston Police Department-HPD,Black,Male,52514
20 | Houston Police Department-HPD,White,Male,61643
21 | Houston Police Department-HPD,White,Male,66614
22 | Houston Police Department-HPD,White,Male,81239
23 | Public Works & Engineering-PWE,Black,Male,29557
24 | Houston Fire Department (HFD),White,Male,62540
25 | Houston Fire Department (HFD),Hispanic,Male,61921
26 | Houston Police Department-HPD,White,Male,66614
27 | Health & Human Services,Black,Female,34923
28 | Public Works & Engineering-PWE,White,Female,60258
29 | Health & Human Services,Asian,Female,67499
30 | Public Works & Engineering-PWE,Black,Male,38168
31 | Houston Fire Department (HFD),Hispanic,Male,43528
32 | Houston Fire Department (HFD),White,Male,48190
33 | Houston Fire Department (HFD),White,Male,48190
34 | Houston Fire Department (HFD),Hispanic,Male,165216
35 | Houston Police Department-HPD,White,Male,45279
36 | Houston Police Department-HPD,White,Male,66614
37 | Public Works & Engineering-PWE,Black,Male,43597
38 | Public Works & Engineering-PWE,Black,Female,43618
39 | Houston Fire Department (HFD),White,Male,62540
40 | Houston Fire Department (HFD),White,Male,66523
41 | Houston Fire Department (HFD),Hispanic,Male,61921
42 | Houston Police Department-HPD,Hispanic,Male,81239
43 | Houston Fire Department (HFD),Hispanic,Male,55437
44 | Houston Airport System (HAS),Hispanic,Male,86297
45 | Houston Fire Department (HFD),White,Male,63166
46 | Houston Police Department-HPD,Black,Female,34757
47 | Public Works & Engineering-PWE,Hispanic,Male,49483
48 | Houston Airport System (HAS),White,Male,40144
49 | Houston Airport System (HAS),Black,Male,28163
50 | Public Works & Engineering-PWE,Hispanic,Male,30992
51 | Houston Fire Department (HFD),Black,Female,96668
52 | Houston Police Department-HPD,White,Male,81239
53 | Public Works & Engineering-PWE,Black,Female,26125
54 | Houston Airport System (HAS),White,Male,43826
55 | Health & Human Services,White,Female,100791
56 | Houston Police Department-HPD,Hispanic,Female,66614
57 | Houston Police Department-HPD,White,Male,66614
58 | Houston Police Department-HPD,Black,Male,66614
59 | Health & Human Services,Hispanic,Female,31387
60 | Houston Police Department-HPD,White,Male,66614
61 | Houston Airport System (HAS),White,Female,36296
62 | Houston Fire Department (HFD),White,Male,78355
63 | Houston Police Department-HPD,Hispanic,Male,52514
64 | Houston Police Department-HPD,Hispanic,Male,43443
65 | Public Works & Engineering-PWE,Black,Female,30347
66 | Public Works & Engineering-PWE,White,Male,44283
67 | Houston Fire Department (HFD),White,Male,62540
68 | Health & Human Services,Black,Female,48984
69 | Houston Fire Department (HFD),White,Male,62540
70 | Public Works & Engineering-PWE,Hispanic,Female,30347
71 | Public Works & Engineering-PWE,Black,Male,53373
72 | Houston Police Department-HPD,Hispanic,Male,66614
73 | Public Works & Engineering-PWE,Hispanic,Male,52156
74 | Parks & Recreation,Black,Female,26125
75 | Houston Fire Department (HFD),Hispanic,Male,66523
76 | Houston Police Department-HPD,Hispanic,Male,77076
77 | Houston Police Department-HPD,Native American,Male,60347
78 | Public Works & Engineering-PWE,Black,Male,29557
79 | Public Works & Engineering-PWE,Black,Male,30139
80 | Houston Airport System (HAS),Hispanic,Female,26125
81 | Houston Fire Department (HFD),White,Male,66523
82 | Houston Fire Department (HFD),Hispanic,Male,51194
83 | Public Works & Engineering-PWE,Black,Female,45926
84 | Houston Police Department-HPD,White,Male,66614
85 | Houston Fire Department (HFD),Black,Male,62540
86 | Houston Fire Department (HFD),White,Male,63166
87 | Health & Human Services,Hispanic,Female,36650
88 | Public Works & Engineering-PWE,Black,Male,41288
89 | Houston Fire Department (HFD),Hispanic,Male,51194
90 | Health & Human Services,Asian,Female,31554
91 | Houston Police Department-HPD,White,Male,91181
92 | Houston Police Department-HPD,Black,Female,66614
93 | Public Works & Engineering-PWE,Hispanic,Male,30597
94 | Health & Human Services,Black,Female,44200
95 | Houston Fire Department (HFD),Black,Male,40170
96 | Houston Fire Department (HFD),White,Male,62540
97 | Houston Police Department-HPD,White,Male,66614
98 | Health & Human Services,White,Male,59819
99 | Houston Police Department-HPD,Hispanic,Male,81239
100 | Houston Police Department-HPD,Hispanic,Female,43443
101 | Houston Police Department-HPD,White,Male,66614
102 | Houston Police Department-HPD,White,Male,81239
103 | Houston Fire Department (HFD),Hispanic,Male,61921
104 | Houston Police Department-HPD,White,Male,66614
105 | Public Works & Engineering-PWE,Black,Male,38646
106 | Houston Police Department-HPD,Black,Male,42000
107 | Parks & Recreation,Black,Male,33592
108 | Public Works & Engineering-PWE,Black,Male,52458
109 | Houston Police Department-HPD,Asian,Male,77076
110 | Houston Fire Department (HFD),Hispanic,Female,51194
111 | Houston Police Department-HPD,White,Female,81239
112 | Houston Police Department-HPD,Black,Female,36317
113 | Health & Human Services,Hispanic,Female,30826
114 | Houston Fire Department (HFD),Hispanic,Male,45791
115 | Houston Police Department-HPD,Black,Male,66614
116 | Houston Fire Department (HFD),White,Male,89590
117 | Houston Airport System (HAS),Black,Male,52499
118 | Public Works & Engineering-PWE,Black,Male,34798
119 | Public Works & Engineering-PWE,Black,Female,35942
120 | Houston Fire Department (HFD),Black,Male,52644
121 | Houston Police Department-HPD,White,Male,60347
122 | Houston Fire Department (HFD),White,Male,63166
123 | Houston Police Department-HPD,Hispanic,Male,55461
124 | Public Works & Engineering-PWE,Black,Male,31491
125 | Houston Police Department-HPD,White,Male,55461
126 | Houston Airport System (HAS),Black,Male,40435
127 | Public Works & Engineering-PWE,Black,Male,30555
128 | Houston Police Department-HPD,Asian,Male,62383
129 | Parks & Recreation,Black,Male,30368
130 | Public Works & Engineering-PWE,Black,Male,40435
131 | Houston Airport System (HAS),Black,Male,43826
132 | Houston Police Department-HPD,Black,Male,74511
133 | Houston Police Department-HPD,Hispanic,Male,47650
134 | Houston Police Department-HPD,Hispanic,Male,66614
135 | Parks & Recreation,Black,Male,29307
136 | Houston Police Department-HPD,Black,Male,81239
137 | Houston Fire Department (HFD),Hispanic,Male,43528
138 | Public Works & Engineering-PWE,Black,Male,56742
139 | Houston Police Department-HPD,White,Male,66614
140 | Houston Airport System (HAS),White,Male,120916
141 | Parks & Recreation,Hispanic,Male,30347
142 | Houston Police Department-HPD,White,Male,55461
143 | Houston Fire Department (HFD),Hispanic,Male,61921
144 | Houston Airport System (HAS),Black,Male,26125
145 | Houston Fire Department (HFD),White,Male,66523
146 | Houston Police Department-HPD,White,Male,66614
147 | Houston Fire Department (HFD),White,Male,210588
148 | Houston Police Department-HPD,Hispanic,Female,47650
149 | Houston Police Department-HPD,Hispanic,Male,59530
150 | Health & Human Services,Black,Female,45906
151 | Houston Police Department-HPD,Black,Male,47650
152 | Houston Police Department-HPD,White,Male,66614
153 | Public Works & Engineering-PWE,Asian,Male,30347
154 | Houston Police Department-HPD,White,Female,66614
155 | Public Works & Engineering-PWE,White,Male,110881
156 | Houston Police Department-HPD,Hispanic,Male,47650
157 | Houston Police Department-HPD,Hispanic,Female,30451
158 | Houston Fire Department (HFD),White,Male,57815
159 | Houston Airport System (HAS),Hispanic,Male,39707
160 | Public Works & Engineering-PWE,Hispanic,Male,28995
161 | Houston Airport System (HAS),White,Male,69514
162 | Public Works & Engineering-PWE,Black,Male,32344
163 | Public Works & Engineering-PWE,White,Male,30992
164 | Houston Police Department-HPD,Asian,Male,66614
165 | Houston Police Department-HPD,Black,Female,66614
166 | Health & Human Services,Black,Female,47382
167 | Public Works & Engineering-PWE,Black,Female,39104
168 | Houston Fire Department (HFD),Hispanic,Male,43528
169 | Houston Airport System (HAS),Asian,Male,62432
170 | Houston Fire Department (HFD),Hispanic,Male,57815
171 | Houston Police Department-HPD,Asian,Male,55461
172 | Health & Human Services,Hispanic,Female,43784
173 | Houston Police Department-HPD,Asian,Male,27914
174 | Houston Fire Department (HFD),White,Male,62540
175 | Public Works & Engineering-PWE,Hispanic,Male,53123
176 | Houston Police Department-HPD,Hispanic,Male,42370
177 | Houston Police Department-HPD,White,Male,66614
178 | Houston Police Department-HPD,White,Male,66614
179 | Houston Fire Department (HFD),Hispanic,Male,62540
180 | Public Works & Engineering-PWE,Black,Male,30347
181 | Houston Fire Department (HFD),Hispanic,Female,58040
182 | Houston Fire Department (HFD),White,Female,74270
183 | Houston Police Department-HPD,White,Female,46675
184 | Houston Police Department-HPD,Black,Female,59182
185 | Houston Police Department-HPD,White,Female,66614
186 | Public Works & Engineering-PWE,Black,Female,33987
187 | Houston Fire Department (HFD),Native American,Male,78355
188 | Public Works & Engineering-PWE,Hispanic,Male,47382
189 | Houston Police Department-HPD,White,Male,81239
190 | Houston Fire Department (HFD),White,Female,45791
191 | Houston Police Department-HPD,White,Male,66614
192 | Houston Police Department-HPD,Asian,Male,62338
193 | Houston Police Department-HPD,Black,Male,66614
194 | Public Works & Engineering-PWE,Black,Male,45490
195 | Houston Police Department-HPD,White,Male,66614
196 | Houston Fire Department (HFD),Hispanic,Male,66523
197 | Houston Fire Department (HFD),Hispanic,Male,40170
198 | Health & Human Services,Hispanic,Male,32802
199 | Houston Police Department-HPD,Hispanic,Male,47650
200 | Houston Police Department-HPD,Black,Male,42000
201 | Public Works & Engineering-PWE,Hispanic,Male,38542
202 | Houston Fire Department (HFD),White,Male,51194
203 | Public Works & Engineering-PWE,Black,Female,35485
204 | Houston Fire Department (HFD),Hispanic,Male,45791
205 | Houston Airport System (HAS),Black,Male,26125
206 | Houston Police Department-HPD,Black,Male,47650
207 | Houston Fire Department (HFD),White,Male,66523
208 | Houston Fire Department (HFD),White,Male,48190
209 | Houston Police Department-HPD,Black,Male,55461
210 | Health & Human Services,White,Female,81849
211 | Houston Police Department-HPD,Black,Female,55461
212 | Health & Human Services,Black,Female,30347
213 | Houston Airport System (HAS),Black,Male,35277
214 | Health & Human Services,Hispanic,Female,39312
215 | Houston Airport System (HAS),White,Male,58677
216 | Houston Fire Department (HFD),White,Male,62540
217 | Houston Fire Department (HFD),White,Male,61921
218 | Houston Fire Department (HFD),White,Male,66523
219 | Houston Police Department-HPD,Hispanic,Male,66614
220 | Health & Human Services,White,Female,34382
221 | Houston Fire Department (HFD),White,Male,51194
222 | Public Works & Engineering-PWE,Hispanic,Male,38438
223 | Houston Police Department-HPD,Black,Male,66614
224 | Houston Police Department-HPD,White,Male,43443
225 | Houston Police Department-HPD,Hispanic,Male,66614
226 | Houston Police Department-HPD,White,Male,52514
227 | Public Works & Engineering-PWE,White,Male,54600
228 | Public Works & Engineering-PWE,Black,Male,54995
229 | Public Works & Engineering-PWE,Hispanic,Male,51584
230 | Houston Fire Department (HFD),Black,Male,70181
231 | Public Works & Engineering-PWE,Black,Male,59748
232 | Public Works & Engineering-PWE,White,Male,141948
233 | Houston Airport System (HAS),Black,Male,100228
234 | Public Works & Engineering-PWE,Hispanic,Male,31450
235 | Public Works & Engineering-PWE,Black,Male,45989
236 | Health & Human Services,Black,Female,81972
237 | Parks & Recreation,Hispanic,Male,33634
238 | Houston Fire Department (HFD),White,Male,84995
239 | Health & Human Services,Hispanic,Female,28434
240 | Houston Police Department-HPD,Black,Male,66614
241 | Houston Police Department-HPD,Hispanic,Male,77076
242 | Houston Airport System (HAS),White,Female,51139
243 | Houston Police Department-HPD,Hispanic,Male,45279
244 | Public Works & Engineering-PWE,Black,Female,27373
245 | Houston Police Department-HPD,Black,Male,55461
246 | Houston Police Department-HPD,White,Male,91181
247 | Houston Police Department-HPD,White,Male,66614
248 | Houston Airport System (HAS),Hispanic,Female,68451
249 | Parks & Recreation,Hispanic,Female,36920
250 | Houston Police Department-HPD,White,Male,77076
251 | Houston Fire Department (HFD),Hispanic,Female,45791
252 | Houston Police Department-HPD,Black,Male,81239
253 | Houston Airport System (HAS),White,Male,65832
254 | Houston Police Department-HPD,Hispanic,Male,55461
255 | Houston Police Department-HPD,Black,Male,50621
256 | Houston Fire Department (HFD),Black,Male,52644
257 | Houston Fire Department (HFD),Hispanic,Female,40170
258 | Houston Fire Department (HFD),Hispanic,Male,51194
259 | Houston Fire Department (HFD),White,Female,63166
260 | Houston Fire Department (HFD),Hispanic,Male,48190
261 | Houston Police Department-HPD,White,Male,47650
262 | Houston Police Department-HPD,White,Female,60347
263 | Houston Fire Department (HFD),White,Female,40170
264 | Houston Police Department-HPD,Black,Male,61643
265 | Health & Human Services,Hispanic,Female,32864
266 | Public Works & Engineering-PWE,White,Male,56742
267 | Public Works & Engineering-PWE,Black,Male,41288
268 | Parks & Recreation,Black,Female,48090
269 | Houston Police Department-HPD,White,Male,60347
270 | Houston Police Department-HPD,Black,Male,66614
271 | Houston Fire Department (HFD),Black,Male,51194
272 | Houston Police Department-HPD,Asian,Male,61643
273 | Public Works & Engineering-PWE,Black,Male,30597
274 | Public Works & Engineering-PWE,Hispanic,Male,46800
275 | Houston Police Department-HPD,Hispanic,Male,50621
276 | Houston Fire Department (HFD),White,Male,61921
277 | Public Works & Engineering-PWE,White,Male,63315
278 | Houston Police Department-HPD,White,Male,66614
279 | Public Works & Engineering-PWE,Hispanic,Female,30347
280 | Public Works & Engineering-PWE,Black,Male,39978
281 | Houston Police Department-HPD,Hispanic,Female,47650
282 | Parks & Recreation,Black,Male,54600
283 | Houston Police Department-HPD,Black,Female,86534
284 | Houston Police Department-HPD,Hispanic,Female,37294
285 | Houston Fire Department (HFD),White,Male,62540
286 | Houston Police Department-HPD,White,Male,50621
287 | Houston Fire Department (HFD),Hispanic,Male,55437
288 | Houston Fire Department (HFD),Black,Male,66523
289 | Houston Police Department-HPD,Black,Male,45279
290 | Parks & Recreation,Black,Male,33488
291 | Houston Fire Department (HFD),White,Male,51194
292 | Public Works & Engineering-PWE,Black,Male,28995
293 | Public Works & Engineering-PWE,Asian,Female,90716
294 | Houston Airport System (HAS),Hispanic,Male,41808
295 | Health & Human Services,Asian,Female,94149
296 | Public Works & Engineering-PWE,White,Female,73783
297 | Houston Police Department-HPD,Black,Male,81239
298 | Houston Fire Department (HFD),Black,Male,55437
299 | Houston Police Department-HPD,Hispanic,Female,45279
300 | Houston Fire Department (HFD),Hispanic,Female,48190
301 | Houston Fire Department (HFD),Hispanic,Male,51194
302 | Public Works & Engineering-PWE,Black,Male,54600
303 | Houston Police Department-HPD,White,Male,55461
304 | Public Works & Engineering-PWE,White,Male,39478
305 | Houston Police Department-HPD,White,Male,61643
306 | Houston Police Department-HPD,Hispanic,Male,55461
307 | Public Works & Engineering-PWE,Black,Female,28600
308 | Public Works & Engineering-PWE,White,Male,44096
309 | Houston Police Department-HPD,Black,Male,81239
310 | Public Works & Engineering-PWE,Black,Female,60133
311 | Health & Human Services,White,Male,52072
312 | Houston Fire Department (HFD),White,Male,62540
313 | Public Works & Engineering-PWE,Asian,Female,51168
314 | Public Works & Engineering-PWE,Black,Male,32240
315 | Public Works & Engineering-PWE,Hispanic,Female,33488
316 | Houston Fire Department (HFD),White,Male,61921
317 | Houston Police Department-HPD,Black,Male,45279
318 | Houston Police Department-HPD,Asian,Male,50621
319 | Houston Fire Department (HFD),White,Male,62540
320 | Public Works & Engineering-PWE,Black,Male,53851
321 | Houston Police Department-HPD,White,Male,104455
322 | Houston Police Department-HPD,White,Male,55461
323 | Houston Fire Department (HFD),Black,Male,52644
324 | Houston Police Department-HPD,Black,Female,36317
325 | Houston Police Department-HPD,Black,Male,27893
326 | Public Works & Engineering-PWE,Black,Male,42266
327 | Houston Police Department-HPD,Black,Female,44970
328 | Houston Fire Department (HFD),White,Male,61226
329 | Public Works & Engineering-PWE,Black,Male,35797
330 | Houston Airport System (HAS),White,Female,88003
331 | Houston Fire Department (HFD),White,Male,52644
332 | Houston Police Department-HPD,Hispanic,Male,66614
333 | Houston Fire Department (HFD),White,Male,55437
334 | Health & Human Services,Black,Female,38189
335 | Houston Police Department-HPD,White,Male,61643
336 | Health & Human Services,Black,Female,76246
337 | Public Works & Engineering-PWE,Hispanic,Female,40352
338 | Houston Police Department-HPD,White,Male,66614
339 | Houston Fire Department (HFD),White,Male,70181
340 | Public Works & Engineering-PWE,Hispanic,Male,30597
341 | Public Works & Engineering-PWE,Black,Female,39666
342 | Houston Fire Department (HFD),White,Male,55437
343 | Houston Fire Department (HFD),White,Male,89590
344 | Health & Human Services,Hispanic,Female,30410
345 | Health & Human Services,Black,Female,30597
346 | Houston Airport System (HAS),White,Male,27851
347 | Houston Police Department-HPD,White,Male,66614
348 | Houston Fire Department (HFD),White,Male,55437
349 | Houston Police Department-HPD,Black,Female,24960
350 | Houston Police Department-HPD,Black,Male,47650
351 | Health & Human Services,Hispanic,Female,34299
352 | Health & Human Services,Black,Female,49379
353 | Public Works & Engineering-PWE,Black,Male,51605
354 | Public Works & Engineering-PWE,Black,Male,32968
355 | Houston Airport System (HAS),Hispanic,Female,26125
356 | Houston Police Department-HPD,Black,Female,66614
357 | Houston Police Department-HPD,Black,Male,60347
358 | Public Works & Engineering-PWE,Black,Female,35485
359 | Houston Fire Department (HFD),White,Male,62540
360 | Public Works & Engineering-PWE,Black,Female,30347
361 | Health & Human Services,Black,Female,31470
362 | Houston Police Department-HPD,White,Female,60347
363 | Public Works & Engineering-PWE,White,Male,63461
364 | Houston Police Department-HPD,Asian,Female,91181
365 | Public Works & Engineering-PWE,Black,Female,70780
366 | Houston Police Department-HPD,Asian,Male,81239
367 | Houston Police Department-HPD,Hispanic,Male,61643
368 | Houston Police Department-HPD,Black,Female,35318
369 | Houston Fire Department (HFD),White,Male,40170
370 | Houston Police Department-HPD,Hispanic,Male,50621
371 | Health & Human Services,Hispanic,Female,29245
372 | Houston Police Department-HPD,White,Male,81239
373 | Public Works & Engineering-PWE,Black,Male,83744
374 | Parks & Recreation,Black,Female,56826
375 | Houston Fire Department (HFD),White,Male,61226
376 | Public Works & Engineering-PWE,Hispanic,Male,26104
377 | Public Works & Engineering-PWE,White,Male,59613
378 | Houston Police Department-HPD,White,Male,66614
379 | Houston Police Department-HPD,White,Male,66614
380 | Houston Fire Department (HFD),White,Male,43528
381 | Houston Fire Department (HFD),White,Male,61921
382 | Public Works & Engineering-PWE,White,Male,93089
383 | Public Works & Engineering-PWE,Asian,Male,64432
384 | Public Works & Engineering-PWE,White,Female,53726
385 | Houston Police Department-HPD,White,Male,66614
386 | Public Works & Engineering-PWE,White,Female,34029
387 | Health & Human Services,Hispanic,Female,28600
388 | Houston Police Department-HPD,Hispanic,Male,66614
389 | Houston Police Department-HPD,Hispanic,Male,61643
390 | Houston Police Department-HPD,White,Male,66614
391 | Houston Police Department-HPD,Black,Female,81239
392 | Houston Police Department-HPD,Hispanic,Male,66614
393 | Houston Fire Department (HFD),Hispanic,Male,48190
394 | Houston Police Department-HPD,White,Male,81239
395 | Public Works & Engineering-PWE,Black,Male,30992
396 | Parks & Recreation,Black,Male,35027
397 | Houston Police Department-HPD,Hispanic,Female,60347
398 | Houston Airport System (HAS),Black,Female,110686
399 | Houston Police Department-HPD,White,Male,66614
400 | Public Works & Engineering-PWE,Black,Female,45157
401 | Houston Police Department-HPD,Black,Female,52514
402 | Houston Police Department-HPD,White,Male,43443
403 | Houston Fire Department (HFD),Black,Male,28024
404 | Houston Police Department-HPD,White,Female,66614
405 | Public Works & Engineering-PWE,Black,Male,36130
406 | Houston Police Department-HPD,White,Male,47650
407 | Houston Fire Department (HFD),Hispanic,Male,66523
408 | Houston Airport System (HAS),Black,Male,43826
409 | Houston Police Department-HPD,Hispanic,Female,43443
410 | Houston Fire Department (HFD),White,Male,61226
411 | Public Works & Engineering-PWE,White,Male,54600
412 | Houston Fire Department (HFD),White,Male,61226
413 | Public Works & Engineering-PWE,Black,Male,34861
414 | Public Works & Engineering-PWE,Black,Female,56992
415 | Houston Fire Department (HFD),White,Male,45791
416 | Houston Police Department-HPD,White,Male,50621
417 | Houston Police Department-HPD,Black,Male,47650
418 | Parks & Recreation,Hispanic,Male,26125
419 | Health & Human Services,Hispanic,Female,38709
420 | Public Works & Engineering-PWE,Black,Male,34320
421 | Houston Police Department-HPD,Black,Male,35838
422 | Health & Human Services,Black,Male,60496
423 | Houston Police Department-HPD,Hispanic,Male,55461
424 | Public Works & Engineering-PWE,White,Male,90716
425 | Public Works & Engineering-PWE,Asian,Male,55695
426 | Houston Fire Department (HFD),Hispanic,Male,55437
427 | Houston Police Department-HPD,Black,Female,60347
428 | Houston Police Department-HPD,Black,Male,61643
429 | Houston Police Department-HPD,White,Male,66614
430 | Houston Police Department-HPD,White,Female,61643
431 | Houston Police Department-HPD,White,Female,81239
432 | Public Works & Engineering-PWE,Black,Female,40851
433 | Houston Fire Department (HFD),White,Male,61226
434 | Public Works & Engineering-PWE,Black,Female,30347
435 | Houston Fire Department (HFD),Hispanic,Male,61921
436 | Public Works & Engineering-PWE,Black,Male,45926
437 | Public Works & Engineering-PWE,White,Male,56576
438 | Public Works & Engineering-PWE,White,Male,58157
439 | Houston Police Department-HPD,Black,Female,55461
440 | Houston Police Department-HPD,White,Male,55461
441 | Public Works & Engineering-PWE,Black,Female,51563
442 | Houston Fire Department (HFD),White,Male,61921
443 | Public Works & Engineering-PWE,Black,Female,52957
444 | Houston Airport System (HAS),Asian,Male,37003
445 | Houston Police Department-HPD,Hispanic,Male,61643
446 | Public Works & Engineering-PWE,White,Male,40622
447 | Houston Police Department-HPD,White,Female,66614
448 | Parks & Recreation,Hispanic,Male,36920
449 | Houston Fire Department (HFD),Black,Male,61226
450 | Public Works & Engineering-PWE,White,Female,105503
451 | Houston Police Department-HPD,White,Female,99953
452 | Public Works & Engineering-PWE,Hispanic,Male,63338
453 | Public Works & Engineering-PWE,White,Female,90715
454 | Houston Police Department-HPD,White,Male,60347
455 | Public Works & Engineering-PWE,Black,Female,33488
456 | Houston Police Department-HPD,White,Female,35318
457 | Houston Airport System (HAS),Hispanic,Male,35318
458 | Houston Police Department-HPD,White,Male,74511
459 | Houston Airport System (HAS),Black,Female,150416
460 | Houston Police Department-HPD,Hispanic,Male,66614
461 | Houston Police Department-HPD,Asian,Male,61643
462 | Houston Fire Department (HFD),Asian,Male,43528
463 | Houston Police Department-HPD,Hispanic,Female,47650
464 | Health & Human Services,Black,Female,45614
465 | Houston Fire Department (HFD),White,Male,51194
466 | Houston Fire Department (HFD),White,Male,63166
467 | Houston Police Department-HPD,Hispanic,Male,61643
468 | Houston Police Department-HPD,White,Male,66614
469 | Houston Police Department-HPD,Black,Male,55461
470 | Houston Police Department-HPD,Black,Female,29827
471 | Houston Airport System (HAS),Black,Female,26125
472 | Public Works & Engineering-PWE,White,Male,29765
473 | Parks & Recreation,Hispanic,Male,29910
474 | Parks & Recreation,Black,Male,26125
475 | Houston Police Department-HPD,Black,Male,42000
476 | Public Works & Engineering-PWE,Black,Female,32406
477 | Health & Human Services,Black,Female,31387
478 | Houston Fire Department (HFD),White,Male,70181
479 | Public Works & Engineering-PWE,Black,Female,27373
480 | Houston Police Department-HPD,Hispanic,Male,81239
481 | Houston Fire Department (HFD),Hispanic,Male,61226
482 | Houston Police Department-HPD,Hispanic,Male,50621
483 | Public Works & Engineering-PWE,Hispanic,Male,31158
484 | Public Works & Engineering-PWE,Hispanic,Male,84500
485 | Houston Police Department-HPD,Black,Male,41621
486 | Houston Fire Department (HFD),White,Male,40170
487 | Health & Human Services,Hispanic,Male,31803
488 | Houston Fire Department (HFD),Hispanic,Male,57815
489 | Health & Human Services,Asian,Male,46010
490 | Health & Human Services,White,Male,47362
491 | Public Works & Engineering-PWE,Asian,Female,95950
492 | Houston Fire Department (HFD),Hispanic,Male,78355
493 | Houston Police Department-HPD,White,Male,45279
494 | Public Works & Engineering-PWE,Black,Female,29931
495 | Houston Police Department-HPD,Black,Male,66614
496 | Public Works & Engineering-PWE,Black,Female,34757
497 | Parks & Recreation,Hispanic,Female,42640
498 | Parks & Recreation,Black,Female,57533
499 | Houston Police Department-HPD,White,Male,66614
500 | Public Works & Engineering-PWE,Black,Female,30347
501 | Houston Police Department-HPD,Black,Female,32261
502 | Houston Police Department-HPD,Hispanic,Female,47650
503 | Houston Police Department-HPD,Hispanic,Male,66614
504 | Houston Fire Department (HFD),White,Male,70181
505 | Health & Human Services,Black,Female,63785
506 | Parks & Recreation,Black,Male,28205
507 | Houston Fire Department (HFD),White,Male,45791
508 | Houston Police Department-HPD,White,Male,66614
509 | Houston Fire Department (HFD),Hispanic,Male,40170
510 | Houston Police Department-HPD,Hispanic,Male,60347
511 | Public Works & Engineering-PWE,White,Female,98752
512 | Public Works & Engineering-PWE,Black,Female,32968
513 | Houston Fire Department (HFD),Hispanic,Male,45791
514 | Houston Police Department-HPD,White,Male,47650
515 | Houston Fire Department (HFD),White,Male,62540
516 | Public Works & Engineering-PWE,Black,Male,34653
517 | Houston Fire Department (HFD),White,Male,45791
518 | Public Works & Engineering-PWE,Black,Male,32968
519 | Houston Police Department-HPD,Hispanic,Male,47650
520 | Houston Police Department-HPD,Black,Male,66614
521 | Houston Police Department-HPD,White,Male,60347
522 | Houston Police Department-HPD,Hispanic,Female,66614
523 | Public Works & Engineering-PWE,Hispanic,Male,36920
524 | Houston Police Department-HPD,White,Male,66614
525 | Houston Police Department-HPD,Hispanic,Male,50621
526 | Public Works & Engineering-PWE,Hispanic,Female,44866
527 | Houston Police Department-HPD,Black,Male,66614
528 | Public Works & Engineering-PWE,Hispanic,Male,53581
529 | Houston Police Department-HPD,White,Male,43443
530 | Houston Airport System (HAS),Black,Female,39229
531 | Houston Police Department-HPD,Black,Female,29890
532 | Houston Police Department-HPD,Black,Male,81239
533 | Houston Fire Department (HFD),White,Male,61921
534 | Houston Fire Department (HFD),White,Male,66523
535 | Houston Airport System (HAS),Hispanic,Male,33238
536 | Public Works & Engineering-PWE,Asian,Male,34611
537 | Houston Fire Department (HFD),Hispanic,Female,41267
538 | Houston Police Department-HPD,White,Male,55461
539 | Houston Fire Department (HFD),White,Male,70181
540 | Houston Police Department-HPD,Black,Male,43035
541 | Houston Police Department-HPD,Asian,Female,35152
542 | Houston Fire Department (HFD),Hispanic,Male,70181
543 | Houston Fire Department (HFD),White,Male,70181
544 | Houston Fire Department (HFD),Black,Male,66523
545 | Houston Fire Department (HFD),Black,Male,130585
546 | Houston Police Department-HPD,Black,Female,66614
547 | Houston Fire Department (HFD),Hispanic,Female,66523
548 | Houston Police Department-HPD,White,Male,28169
549 | Public Works & Engineering-PWE,Black,Male,42182
550 | Public Works & Engineering-PWE,Black,Male,29557
551 | Houston Fire Department (HFD),White,Male,52644
552 | Houston Police Department-HPD,Black,Male,66614
553 | Public Works & Engineering-PWE,Black,Female,48360
554 | Houston Police Department-HPD,Asian,Male,163228
555 | Houston Airport System (HAS),Hispanic,Male,39125
556 | Houston Fire Department (HFD),Black,Male,51194
557 | Houston Police Department-HPD,Black,Female,58409
558 | Houston Fire Department (HFD),White,Male,66523
559 | Houston Fire Department (HFD),White,Male,61921
560 | Public Works & Engineering-PWE,Black,Female,37440
561 | Houston Police Department-HPD,Hispanic,Male,66614
562 | Houston Police Department-HPD,Hispanic,Male,52514
563 | Houston Airport System (HAS),White,Male,96284
564 | Public Works & Engineering-PWE,Hispanic,Male,90293
565 | Houston Fire Department (HFD),Hispanic,Male,48190
566 | Houston Fire Department (HFD),White,Male,62540
567 | Health & Human Services,Black,Female,57433
568 | Houston Fire Department (HFD),White,Male,51194
569 | Houston Police Department-HPD,White,Male,55461
570 | Public Works & Engineering-PWE,Asian,Male,31408
571 | Parks & Recreation,Hispanic,Male,46580
572 | Public Works & Engineering-PWE,Black,Female,40581
573 | Houston Police Department-HPD,White,Female,45552
574 | Houston Police Department-HPD,Black,Male,47650
575 | Houston Police Department-HPD,White,Female,50621
576 | Houston Fire Department (HFD),Black,Male,43528
577 | Houston Fire Department (HFD),White,Male,55437
578 | Houston Police Department-HPD,White,Male,81239
579 | Houston Police Department-HPD,White,Male,60347
580 | Health & Human Services,Hispanic,Female,37835
581 | Public Works & Engineering-PWE,Black,Male,30139
582 | Houston Police Department-HPD,Black,Male,81239
583 | Public Works & Engineering-PWE,Asian,Male,50960
584 | Health & Human Services,Hispanic,Female,34008
585 | Houston Fire Department (HFD),Hispanic,Male,55437
586 | Houston Police Department-HPD,Hispanic,Female,27914
587 | Health & Human Services,Hispanic,Female,32198
588 | Houston Police Department-HPD,Hispanic,Male,81239
589 | Houston Fire Department (HFD),Black,Male,40170
590 | Houston Police Department-HPD,White,Male,199596
591 | Public Works & Engineering-PWE,White,Male,57304
592 | Houston Police Department-HPD,White,Male,66614
593 | Parks & Recreation,Hispanic,Male,26125
594 | Houston Police Department-HPD,Asian,Female,35880
595 | Public Works & Engineering-PWE,Black,Male,31762
596 | Houston Police Department-HPD,Hispanic,Male,42000
597 | Public Works & Engineering-PWE,Black,Male,27373
598 | Parks & Recreation,Black,Female,31075
599 | Houston Police Department-HPD,Hispanic,Male,81239
600 | Houston Fire Department (HFD),Black,Male,43528
601 | Health & Human Services,Black,Female,34320
602 | Parks & Recreation,Black,Male,36920
603 | Houston Fire Department (HFD),White,Male,78355
604 | Houston Fire Department (HFD),White,Male,66523
605 | Public Works & Engineering-PWE,Black,Male,41288
606 | Houston Fire Department (HFD),Black,Male,70181
607 | Houston Police Department-HPD,Hispanic,Male,74511
608 | Houston Fire Department (HFD),White,Male,45791
609 | Public Works & Engineering-PWE,Black,Male,28995
610 | Public Works & Engineering-PWE,White,Male,63336
611 | Houston Airport System (HAS),White,Female,100873
612 | Houston Police Department-HPD,Black,Male,66614
613 | Houston Police Department-HPD,Asian,Male,81239
614 | Houston Police Department-HPD,Hispanic,Male,91181
615 | Houston Police Department-HPD,Hispanic,Female,60347
616 | Public Works & Engineering-PWE,White,Male,31429
617 | Houston Fire Department (HFD),White,Male,89590
618 | Houston Fire Department (HFD),White,Male,78355
619 | Houston Police Department-HPD,Hispanic,Female,55461
620 | Houston Fire Department (HFD),Black,Male,61226
621 | Houston Fire Department (HFD),White,Male,99480
622 | Houston Police Department-HPD,White,Male,66614
623 | Houston Police Department-HPD,Asian,Female,66614
624 | Public Works & Engineering-PWE,Black,Male,36837
625 | Houston Fire Department (HFD),Black,Male,61226
626 | Health & Human Services,Black,Female,83174
627 | Houston Fire Department (HFD),Hispanic,Male,61226
628 | Houston Fire Department (HFD),Black,Male,48190
629 | Houston Fire Department (HFD),Black,Male,43528
630 | Houston Police Department-HPD,Asian,Male,66614
631 | Houston Police Department-HPD,White,Male,55461
632 | Houston Fire Department (HFD),White,Male,51194
633 | Houston Fire Department (HFD),White,Male,40170
634 | Health & Human Services,Hispanic,Female,65589
635 | Houston Police Department-HPD,White,Male,43443
636 | Houston Police Department-HPD,White,Male,66614
637 | Houston Police Department-HPD,Hispanic,Female,33322
638 | Houston Police Department-HPD,Hispanic,Female,47008
639 | Houston Fire Department (HFD),Black,Male,70181
640 | Houston Police Department-HPD,Black,Female,32490
641 | Parks & Recreation,White,Male,37565
642 | Houston Police Department-HPD,White,Male,86534
643 | Health & Human Services,Black,Female,82915
644 | Houston Police Department-HPD,White,Male,66614
645 | Houston Fire Department (HFD),Hispanic,Male,51194
646 | Houston Police Department-HPD,Black,Male,61643
647 | Public Works & Engineering-PWE,Hispanic,Male,40518
648 | Houston Fire Department (HFD),Hispanic,Male,40170
649 | Public Works & Engineering-PWE,Asian,Male,79953
650 | Houston Police Department-HPD,White,Male,66614
651 | Public Works & Engineering-PWE,Hispanic,Male,46238
652 | Houston Fire Department (HFD),White,Male,70181
653 | Health & Human Services,Black,Female,46384
654 | Public Works & Engineering-PWE,Black,Male,30597
655 | Houston Airport System (HAS),Black,Male,186192
656 | Houston Police Department-HPD,Hispanic,Male,66614
657 | Houston Police Department-HPD,Asian,Male,28169
658 | Houston Fire Department (HFD),Hispanic,Male,43528
659 | Houston Police Department-HPD,Hispanic,Female,43035
660 | Public Works & Engineering-PWE,White,Male,46550
661 | Houston Fire Department (HFD),Hispanic,Male,55437
662 | Houston Fire Department (HFD),White,Male,70181
663 | Houston Police Department-HPD,Hispanic,Male,66614
664 | Houston Police Department-HPD,Native American,Male,81239
665 | Houston Fire Department (HFD),White,Male,45791
666 | Public Works & Engineering-PWE,White,Female,70565
667 | Houston Police Department-HPD,Hispanic,Male,81239
668 | Houston Police Department-HPD,White,Male,66614
669 | Houston Police Department-HPD,White,Male,55461
670 | Houston Fire Department (HFD),White,Male,62540
671 | Houston Police Department-HPD,White,Male,66614
672 | Houston Police Department-HPD,Black,Male,66614
673 | Houston Police Department-HPD,Asian,Male,50378
674 | Houston Police Department-HPD,Hispanic,Male,81239
675 | Houston Fire Department (HFD),White,Male,55437
676 | Houston Fire Department (HFD),White,Male,61921
677 | Public Works & Engineering-PWE,Hispanic,Male,30139
678 | Public Works & Engineering-PWE,Black,Male,66310
679 | Houston Police Department-HPD,White,Male,69661
680 | Houston Police Department-HPD,Black,Male,66614
681 | Houston Fire Department (HFD),White,Male,70181
682 | Health & Human Services,Black,Female,31782
683 | Houston Police Department-HPD,Black,Female,66614
684 | Public Works & Engineering-PWE,White,Male,86288
685 | Public Works & Engineering-PWE,Asian,Male,87236
686 | Houston Fire Department (HFD),White,Male,63166
687 | Houston Airport System (HAS),Hispanic,Female,29640
688 | Houston Police Department-HPD,Black,Male,47650
689 | Public Works & Engineering-PWE,White,Male,105354
690 | Houston Airport System (HAS),Black,Male,47237
691 | Houston Police Department-HPD,White,Male,66614
692 | Houston Police Department-HPD,Black,Female,34528
693 | Parks & Recreation,Black,Male,50028
694 | Houston Airport System (HAS),Black,Male,48568
695 | Houston Police Department-HPD,White,Male,81239
696 | Houston Police Department-HPD,Black,Female,26125
697 | Public Works & Engineering-PWE,Black,Male,34320
698 | Houston Police Department-HPD,Hispanic,Male,55461
699 | Houston Airport System (HAS),Black,Male,62650
700 | Public Works & Engineering-PWE,Black,Male,32968
701 | Public Works & Engineering-PWE,Asian,Male,44096
702 | Public Works & Engineering-PWE,Hispanic,Male,54600
703 | Public Works & Engineering-PWE,Hispanic,Female,45178
704 | Health & Human Services,Hispanic,Female,40976
705 | Health & Human Services,Black,Female,70762
706 | Houston Fire Department (HFD),White,Male,51194
707 | Houston Fire Department (HFD),White,Male,43528
708 | Houston Police Department-HPD,White,Male,66614
709 | Houston Police Department-HPD,Black,Female,55461
710 | Houston Police Department-HPD,Hispanic,Male,66614
711 | Public Works & Engineering-PWE,Black,Male,36213
712 | Houston Police Department-HPD,Black,Male,77076
713 | Public Works & Engineering-PWE,Black,Male,71170
714 | Health & Human Services,Asian,Male,52624
715 | Public Works & Engineering-PWE,White,Male,37211
716 | Houston Police Department-HPD,Black,Female,37336
717 | Houston Police Department-HPD,Black,Male,66614
718 | Houston Police Department-HPD,Black,Male,60653
719 | Houston Police Department-HPD,Black,Female,81239
720 | Houston Fire Department (HFD),Black,Male,43528
721 | Health & Human Services,Black,Female,40144
722 | Houston Police Department-HPD,White,Male,42000
723 | Houston Fire Department (HFD),Hispanic,Male,70181
724 | Houston Police Department-HPD,White,Male,61643
725 | Houston Police Department-HPD,Hispanic,Male,47650
726 | Houston Police Department-HPD,Asian,Male,66614
727 | Public Works & Engineering-PWE,Hispanic,Female,66449
728 | Houston Fire Department (HFD),White,Male,74270
729 | Houston Police Department-HPD,Asian,Male,45279
730 | Houston Airport System (HAS),Black,Male,27082
731 | Health & Human Services,White,Male,47632
732 | Houston Fire Department (HFD),White,Male,43528
733 | Houston Police Department-HPD,White,Female,91181
734 | Public Works & Engineering-PWE,Black,Male,42640
735 | Public Works & Engineering-PWE,Hispanic,Female,62426
736 | Houston Police Department-HPD,Hispanic,Male,66614
737 | Houston Police Department-HPD,White,Male,81239
738 | Public Works & Engineering-PWE,Black,Male,44429
739 | Houston Police Department-HPD,Asian,Male,52514
740 | Houston Police Department-HPD,Hispanic,Male,66614
741 | Public Works & Engineering-PWE,Black,Female,86763
742 | Public Works & Engineering-PWE,Asian,Male,47382
743 | Houston Police Department-HPD,Black,Female,31907
744 | Health & Human Services,Black,Female,48027
745 | Houston Fire Department (HFD),White,Male,62540
746 | Houston Police Department-HPD,Hispanic,Male,60347
747 | Public Works & Engineering-PWE,Black,Male,54995
748 | Houston Fire Department (HFD),White,Male,70181
749 | Houston Police Department-HPD,White,Male,30888
750 | Houston Police Department-HPD,Black,Male,60347
751 | Houston Fire Department (HFD),Black,Male,63166
752 | Houston Police Department-HPD,Hispanic,Male,66614
753 | Health & Human Services,Hispanic,Male,46363
754 | Houston Police Department-HPD,White,Male,81239
755 | Houston Fire Department (HFD),Hispanic,Male,52644
756 | Houston Police Department-HPD,White,Male,66614
757 | Houston Police Department-HPD,Hispanic,Male,66614
758 | Public Works & Engineering-PWE,Hispanic,Male,57470
759 | Parks & Recreation,Black,Male,56014
760 | Public Works & Engineering-PWE,Black,Female,77899
761 | Houston Police Department-HPD,Black,Male,77076
762 | Houston Police Department-HPD,Hispanic,Female,26125
763 | Houston Fire Department (HFD),Black,Male,62540
764 | Houston Police Department-HPD,Black,Female,31845
765 | Public Works & Engineering-PWE,Black,Male,39666
766 | Health & Human Services,Black,Male,38709
767 | Houston Police Department-HPD,Hispanic,Male,66614
768 | Houston Airport System (HAS),White,Female,35256
769 | Houston Police Department-HPD,White,Male,81239
770 | Public Works & Engineering-PWE,White,Male,34632
771 | Public Works & Engineering-PWE,Black,Male,37128
772 | Houston Police Department-HPD,White,Male,66614
773 | Health & Human Services,Black,Male,53832
774 | Houston Police Department-HPD,White,Male,55461
775 | Houston Police Department-HPD,Black,Female,30888
776 | Houston Fire Department (HFD),White,Male,63166
777 | Houston Police Department-HPD,Black,Male,66614
778 | Houston Fire Department (HFD),White,Male,63166
779 | Houston Airport System (HAS),White,Male,51501
780 | Houston Airport System (HAS),Hispanic,Male,31158
781 | Houston Fire Department (HFD),White,Male,61226
782 | Houston Police Department-HPD,Asian,Male,61643
783 | Public Works & Engineering-PWE,Hispanic,Male,41683
784 | Parks & Recreation,Black,Male,30347
785 | Houston Police Department-HPD,White,Female,66614
786 | Houston Police Department-HPD,White,Male,66614
787 | Houston Police Department-HPD,White,Male,55453
788 | Houston Fire Department (HFD),White,Male,78355
789 | Health & Human Services,White,Male,120799
790 | Houston Police Department-HPD,Hispanic,Male,43443
791 | Parks & Recreation,Black,Male,28766
792 | Houston Fire Department (HFD),White,Male,89590
793 | Houston Police Department-HPD,Hispanic,Female,66614
794 | Houston Fire Department (HFD),White,Male,74270
795 | Houston Fire Department (HFD),Hispanic,Male,57815
796 | Houston Police Department-HPD,White,Male,66614
797 | Houston Police Department-HPD,Hispanic,Female,55461
798 | Public Works & Engineering-PWE,Hispanic,Male,53435
799 | Health & Human Services,Native American,Female,49379
800 | Public Works & Engineering-PWE,Hispanic,Male,48776
801 | Houston Police Department-HPD,White,Male,91181
802 | Houston Fire Department (HFD),Hispanic,Male,89590
803 | Houston Fire Department (HFD),White,Male,70181
804 | Public Works & Engineering-PWE,White,Male,78076
805 | Houston Fire Department (HFD),White,Male,70181
806 | Houston Fire Department (HFD),Hispanic,Male,89590
807 | Houston Police Department-HPD,White,Male,81239
808 | Public Works & Engineering-PWE,Black,Male,40186
809 | Public Works & Engineering-PWE,Hispanic,Male,69493
810 | Houston Police Department-HPD,Hispanic,Female,47650
811 | Houston Fire Department (HFD),Hispanic,Male,62540
812 | Parks & Recreation,Black,Female,26125
813 | Houston Fire Department (HFD),White,Male,61921
814 | Houston Police Department-HPD,Black,Female,35318
815 | Public Works & Engineering-PWE,Hispanic,Male,45989
816 | Public Works & Engineering-PWE,Hispanic,Male,70115
817 | Houston Police Department-HPD,White,Female,74961
818 | Public Works & Engineering-PWE,Hispanic,Male,65853
819 | Public Works & Engineering-PWE,White,Female,146141
820 | Houston Police Department-HPD,White,Female,55461
821 | Houston Police Department-HPD,White,Male,55461
822 | Houston Police Department-HPD,Black,Male,45279
823 | Houston Fire Department (HFD),Hispanic,Male,43528
824 | Houston Police Department-HPD,White,Male,55461
825 | Houston Fire Department (HFD),White,Male,63166
826 | Houston Police Department-HPD,White,Male,66614
827 | Houston Fire Department (HFD),Black,Male,40170
828 | Houston Airport System (HAS),Black,Female,80791
829 | Public Works & Engineering-PWE,White,Male,50586
830 | Houston Police Department-HPD,White,Male,66614
831 | Health & Human Services,Black,Female,43909
832 | Houston Police Department-HPD,Asian,Male,43443
833 | Houston Fire Department (HFD),Hispanic,Male,40170
834 | Houston Police Department-HPD,Black,Female,27872
835 | Houston Airport System (HAS),Hispanic,Female,67671
836 | Public Works & Engineering-PWE,Hispanic,Female,45490
837 | Public Works & Engineering-PWE,Asian,Male,102297
838 | Houston Police Department-HPD,Black,Female,66614
839 | Houston Airport System (HAS),Black,Male,37669
840 | Houston Police Department-HPD,White,Female,47650
841 | Houston Airport System (HAS),White,Male,61589
842 | Houston Fire Department (HFD),White,Male,55437
843 | Houston Fire Department (HFD),White,Male,28024
844 | Houston Airport System (HAS),White,Male,103686
845 | Houston Fire Department (HFD),White,Male,28024
846 | Houston Fire Department (HFD),Hispanic,Male,78355
847 | Houston Police Department-HPD,Asian,Male,60347
848 | Houston Police Department-HPD,White,Male,81239
849 | Public Works & Engineering-PWE,Black,Male,49109
850 | Public Works & Engineering-PWE,White,Male,66560
851 | Public Works & Engineering-PWE,Hispanic,Female,42453
852 | Parks & Recreation,Black,Female,35339
853 | Houston Police Department-HPD,White,Male,81239
854 | Health & Human Services,Hispanic,Female,28600
855 | Houston Police Department-HPD,White,Male,81239
856 | Houston Fire Department (HFD),White,Male,62540
857 | Houston Police Department-HPD,Black,Male,60347
858 | Houston Fire Department (HFD),Hispanic,Male,70181
859 | Houston Police Department-HPD,White,Female,104455
860 | Houston Fire Department (HFD),White,Male,57815
861 | Houston Police Department-HPD,Hispanic,Male,55461
862 | Houston Fire Department (HFD),White,Male,48190
863 | Houston Airport System (HAS),Hispanic,Female,26125
864 | Health & Human Services,Black,Female,47382
865 | Houston Police Department-HPD,Black,Female,47278
866 | Houston Fire Department (HFD),Black,Male,45791
867 | Houston Police Department-HPD,White,Male,66614
868 | Houston Fire Department (HFD),White,Male,55437
869 | Houston Police Department-HPD,Hispanic,Male,55461
870 | Houston Fire Department (HFD),Black,Female,34902
871 | Houston Airport System (HAS),Black,Female,33467
872 | Houston Police Department-HPD,Asian,Male,52514
873 | Houston Police Department-HPD,Black,Male,55461
874 | Houston Police Department-HPD,White,Male,55461
875 | Public Works & Engineering-PWE,Hispanic,Female,47008
876 | Public Works & Engineering-PWE,Asian,Male,74097
877 | Houston Fire Department (HFD),White,Male,45791
878 | Houston Fire Department (HFD),Black,Male,52644
879 | Public Works & Engineering-PWE,White,Female,43888
880 | Houston Police Department-HPD,White,Male,45279
881 | Houston Fire Department (HFD),White,Male,40170
882 | Public Works & Engineering-PWE,White,Male,97626
883 | Houston Police Department-HPD,White,Male,42000
884 | Parks & Recreation,White,Male,36920
885 | Houston Police Department-HPD,Black,Female,41704
886 | Houston Fire Department (HFD),White,Male,78355
887 | Public Works & Engineering-PWE,White,Female,57842
888 | Houston Airport System (HAS),Black,Male,45698
889 | Houston Fire Department (HFD),Black,Male,62540
890 | Houston Police Department-HPD,White,Female,45279
891 | Public Works & Engineering-PWE,Black,Female,71680
892 | Public Works & Engineering-PWE,White,Female,35027
893 | Houston Fire Department (HFD),White,Male,63166
894 | Public Works & Engineering-PWE,Asian,Male,124861
895 | Houston Police Department-HPD,Black,Male,66614
896 | Houston Fire Department (HFD),Black,Female,36400
897 | Public Works & Engineering-PWE,Black,Male,49587
898 | Health & Human Services,Hispanic,Male,33654
899 | Houston Police Department-HPD,Hispanic,Female,35318
900 | Houston Police Department-HPD,Black,Male,66614
901 | Public Works & Engineering-PWE,Black,Male,38688
902 | Public Works & Engineering-PWE,White,Female,76085
903 | Public Works & Engineering-PWE,Hispanic,Male,32864
904 | Houston Police Department-HPD,White,Male,66614
905 | Houston Fire Department (HFD),White,Male,66523
906 | Houston Police Department-HPD,White,Male,60347
907 | Houston Fire Department (HFD),Black,Female,78355
908 | Houston Fire Department (HFD),Hispanic,Male,40170
909 | Houston Police Department-HPD,White,Female,60347
910 | Houston Police Department-HPD,White,Male,91181
911 | Houston Police Department-HPD,Black,Male,30618
912 | Houston Fire Department (HFD),Black,Male,63166
913 | Public Works & Engineering-PWE,Hispanic,Female,30493
914 | Houston Fire Department (HFD),Hispanic,Male,48190
915 | Houston Police Department-HPD,Asian,Male,66614
916 | Public Works & Engineering-PWE,Black,Female,92028
917 | Public Works & Engineering-PWE,Black,Male,44429
918 | Parks & Recreation,Hispanic,Male,72280
919 | Public Works & Engineering-PWE,Black,Male,40227
920 | Houston Police Department-HPD,White,Male,81239
921 | Public Works & Engineering-PWE,White,Male,39478
922 | Houston Fire Department (HFD),Black,Male,55437
923 | Houston Police Department-HPD,Asian,Male,42000
924 | Public Works & Engineering-PWE,Asian,Female,80433
925 | Houston Police Department-HPD,Black,Female,52333
926 | Houston Police Department-HPD,White,Male,81239
927 | Health & Human Services,Black,Male,41870
928 | Houston Fire Department (HFD),White,Male,62540
929 | Houston Police Department-HPD,White,Male,66614
930 | Houston Police Department-HPD,Asian,Female,55378
931 | Public Works & Engineering-PWE,Black,Female,30347
932 | Houston Airport System (HAS),Black,Female,26125
933 | Houston Police Department-HPD,Hispanic,Male,55461
934 | Public Works & Engineering-PWE,White,Female,62400
935 | Houston Police Department-HPD,Hispanic,Male,77076
936 | Houston Police Department-HPD,White,Male,65416
937 | Houston Fire Department (HFD),White,Male,51194
938 | Houston Fire Department (HFD),Hispanic,Male,62540
939 | Public Works & Engineering-PWE,Black,Male,31450
940 | Houston Police Department-HPD,Black,Female,55461
941 | Houston Fire Department (HFD),Hispanic,Male,43528
942 | Houston Fire Department (HFD),Hispanic,Male,62540
943 | Public Works & Engineering-PWE,White,Female,178331
944 | Houston Fire Department (HFD),White,Male,78355
945 | Public Works & Engineering-PWE,Black,Male,104389
946 | Houston Fire Department (HFD),Hispanic,Male,57815
947 | Houston Airport System (HAS),White,Male,74420
948 | Houston Police Department-HPD,Black,Female,26229
949 | Houston Fire Department (HFD),Hispanic,Male,61921
950 | Houston Fire Department (HFD),White,Male,51194
951 | Health & Human Services,Black,Male,90305
952 | Houston Police Department-HPD,White,Male,55461
953 | Health & Human Services,Black,Female,54509
954 | Houston Police Department-HPD,Hispanic,Female,77076
955 | Houston Fire Department (HFD),Black,Male,66523
956 | Public Works & Engineering-PWE,Black,Male,27352
957 | Public Works & Engineering-PWE,Black,Female,53173
958 | Houston Airport System (HAS),White,Male,54080
959 | Houston Fire Department (HFD),White,Male,62540
960 | Houston Police Department-HPD,White,Male,81239
961 | Houston Fire Department (HFD),White,Male,62540
962 | Houston Police Department-HPD,Hispanic,Female,66614
963 | Public Works & Engineering-PWE,Hispanic,Male,54683
964 | Houston Police Department-HPD,White,Male,66614
965 | Houston Police Department-HPD,Black,Female,61643
966 | Houston Police Department-HPD,Black,Female,66614
967 | Houston Fire Department (HFD),Hispanic,Male,63166
968 | Houston Police Department-HPD,White,Female,42000
969 | Public Works & Engineering-PWE,Black,Male,38251
970 | Public Works & Engineering-PWE,Black,Female,36837
971 | Health & Human Services,Hispanic,Female,30410
972 | Public Works & Engineering-PWE,White,Male,51584
973 | Public Works & Engineering-PWE,White,Male,57450
974 | Public Works & Engineering-PWE,Asian,Male,57200
975 | Public Works & Engineering-PWE,Hispanic,Female,44096
976 | Houston Police Department-HPD,White,Male,60347
977 | Houston Fire Department (HFD),White,Male,89590
978 | Houston Police Department-HPD,White,Male,55461
979 | Public Works & Engineering-PWE,Black,Male,51085
980 | Houston Airport System (HAS),Black,Female,36587
981 | Public Works & Engineering-PWE,Black,Female,33613
982 | Houston Police Department-HPD,White,Male,66614
983 | Houston Police Department-HPD,White,Male,77076
984 | Public Works & Engineering-PWE,Asian,Male,32635
985 | Houston Police Department-HPD,Hispanic,Male,55461
986 | Public Works & Engineering-PWE,Black,Male,45989
987 | Houston Police Department-HPD,White,Male,91181
988 | Houston Police Department-HPD,White,Male,66614
989 | Houston Police Department-HPD,White,Male,47650
990 | Houston Fire Department (HFD),Black,Male,52644
991 | Public Works & Engineering-PWE,Black,Female,37357
992 | Houston Police Department-HPD,Hispanic,Male,50621
993 | Public Works & Engineering-PWE,Black,Male,39021
994 | Houston Police Department-HPD,Black,Male,77076
995 | Public Works & Engineering-PWE,Asian,Male,40810
996 | Public Works & Engineering-PWE,Black,Male,30160
997 | Houston Airport System (HAS),Black,Female,29286
998 | Houston Airport System (HAS),Black,Male,33176
999 | Houston Police Department-HPD,Black,Male,47650
1000 | Houston Police Department-HPD,White,Male,66614
1001 | Houston Police Department-HPD,Black,Female,66614
1002 | Houston Police Department-HPD,Hispanic,Female,50190
1003 | Houston Fire Department (HFD),White,Male,40170
1004 | Houston Police Department-HPD,Hispanic,Male,41059
1005 | Houston Fire Department (HFD),Hispanic,Male,28024
1006 | Houston Police Department-HPD,Black,Male,81239
1007 | Public Works & Engineering-PWE,Black,Female,36629
1008 | Houston Police Department-HPD,Hispanic,Male,50621
1009 | Houston Airport System (HAS),Hispanic,Male,38730
1010 | Houston Airport System (HAS),Black,Female,89416
1011 | Houston Police Department-HPD,Hispanic,Female,36920
1012 | Houston Fire Department (HFD),Black,Male,40170
1013 | Houston Police Department-HPD,Hispanic,Male,60347
1014 | Houston Police Department-HPD,Black,Female,66614
1015 | Public Works & Engineering-PWE,White,Male,60715
1016 | Houston Police Department-HPD,White,Male,60347
1017 | Houston Police Department-HPD,White,Male,55461
1018 | Houston Police Department-HPD,White,Male,66614
1019 | Houston Airport System (HAS),Hispanic,Female,53518
1020 | Houston Fire Department (HFD),White,Male,74270
1021 | Houston Fire Department (HFD),Hispanic,Male,61921
1022 | Public Works & Engineering-PWE,Asian,Male,98748
1023 | Houston Airport System (HAS),Hispanic,Female,73635
1024 | Houston Fire Department (HFD),White,Male,45791
1025 | Public Works & Engineering-PWE,Hispanic,Female,29453
1026 | Houston Police Department-HPD,Asian,Female,50621
1027 | Houston Fire Department (HFD),Hispanic,Male,51194
1028 | Houston Police Department-HPD,White,Male,50621
1029 | Public Works & Engineering-PWE,White,Male,53123
1030 | Houston Fire Department (HFD),White,Male,40170
1031 | Houston Police Department-HPD,White,Male,45279
1032 | Public Works & Engineering-PWE,Black,Female,43867
1033 | Houston Police Department-HPD,Black,Male,42000
1034 | Public Works & Engineering-PWE,Black,Male,35318
1035 | Parks & Recreation,Black,Male,26125
1036 | Houston Fire Department (HFD),White,Male,70181
1037 | Houston Police Department-HPD,Black,Male,66614
1038 | Public Works & Engineering-PWE,Black,Male,34382
1039 | Houston Fire Department (HFD),White,Male,37918
1040 | Houston Fire Department (HFD),Black,Male,66523
1041 | Health & Human Services,Asian,Male,70864
1042 | Public Works & Engineering-PWE,Black,Female,36754
1043 | Houston Fire Department (HFD),White,Male,52644
1044 | Public Works & Engineering-PWE,White,Female,78728
1045 | Health & Human Services,White,Male,60082
1046 | Houston Fire Department (HFD),White,Male,61921
1047 | Houston Fire Department (HFD),White,Male,78355
1048 | Houston Police Department-HPD,Black,Female,55016
1049 | Houston Police Department-HPD,Black,Male,86534
1050 | Public Works & Engineering-PWE,Hispanic,Male,51563
1051 | Houston Police Department-HPD,White,Male,55461
1052 | Public Works & Engineering-PWE,Asian,Male,113930
1053 | Houston Police Department-HPD,Hispanic,Male,47650
1054 | Houston Fire Department (HFD),Hispanic,Male,63166
1055 | Houston Police Department-HPD,Black,Male,66614
1056 | Public Works & Engineering-PWE,Hispanic,Male,67038
1057 | Public Works & Engineering-PWE,Black,Female,33613
1058 | Houston Airport System (HAS),Hispanic,Female,28579
1059 | Houston Police Department-HPD,Hispanic,Male,61643
1060 | Houston Fire Department (HFD),White,Male,61921
1061 | Houston Police Department-HPD,White,Male,66614
1062 | Houston Police Department-HPD,Asian,Male,66614
1063 | Houston Fire Department (HFD),White,Male,40165
1064 | Houston Fire Department (HFD),Hispanic,Male,61921
1065 | Houston Police Department-HPD,Black,Female,66614
1066 | Houston Fire Department (HFD),White,Male,78355
1067 | Houston Police Department-HPD,White,Male,47650
1068 | Houston Police Department-HPD,Black,Male,27414
1069 | Houston Fire Department (HFD),Hispanic,Male,62540
1070 | Houston Police Department-HPD,Hispanic,Male,60347
1071 | Houston Fire Department (HFD),White,Male,63166
1072 | Houston Police Department-HPD,Hispanic,Male,31907
1073 | Houston Police Department-HPD,Black,Female,30618
1074 | Houston Fire Department (HFD),White,Male,48190
1075 | Houston Fire Department (HFD),Black,Male,28024
1076 | Public Works & Engineering-PWE,Hispanic,Female,33488
1077 | Houston Police Department-HPD,White,Male,47650
1078 | Houston Fire Department (HFD),White,Male,28024
1079 | Houston Police Department-HPD,White,Male,81239
1080 | Public Works & Engineering-PWE,Hispanic,Male,50606
1081 | Houston Police Department-HPD,Hispanic,Male,47650
1082 | Houston Fire Department (HFD),White,Male,70181
1083 | Houston Police Department-HPD,Hispanic,Male,47650
1084 | Public Works & Engineering-PWE,White,Female,54122
1085 | Houston Fire Department (HFD),Black,Male,71174
1086 | Public Works & Engineering-PWE,Black,Female,33488
1087 | Public Works & Engineering-PWE,Hispanic,Female,44575
1088 | Houston Police Department-HPD,White,Male,66614
1089 | Houston Police Department-HPD,Black,Female,60347
1090 | Houston Fire Department (HFD),White,Male,66523
1091 | Houston Police Department-HPD,Black,Female,53186
1092 | Houston Fire Department (HFD),White,Male,63166
1093 | Public Works & Engineering-PWE,Black,Male,38418
1094 | Houston Police Department-HPD,Asian,Male,47650
1095 | Houston Fire Department (HFD),Hispanic,Male,45791
1096 | Public Works & Engineering-PWE,White,Male,58816
1097 | Houston Airport System (HAS),Black,Male,33301
1098 | Parks & Recreation,Hispanic,Male,26125
1099 | Houston Fire Department (HFD),Black,Male,62540
1100 | Public Works & Engineering-PWE,Black,Female,30347
1101 | Health & Human Services,Black,Female,103270
1102 | Houston Police Department-HPD,White,Male,66614
1103 | Health & Human Services,Asian,Male,47050
1104 | Houston Fire Department (HFD),White,Male,70181
1105 | Houston Fire Department (HFD),White,Male,66523
1106 | Parks & Recreation,Hispanic,Male,26125
1107 | Houston Airport System (HAS),Asian,Female,26125
1108 | Public Works & Engineering-PWE,Black,Male,27102
1109 | Houston Fire Department (HFD),White,Male,62540
1110 | Houston Police Department-HPD,Black,Male,66614
1111 | Houston Police Department-HPD,Hispanic,Female,61643
1112 | Houston Airport System (HAS),Hispanic,Male,39125
1113 | Houston Airport System (HAS),Black,Male,39125
1114 | Houston Fire Department (HFD),Hispanic,Male,48190
1115 | Public Works & Engineering-PWE,White,Male,43514
1116 | Public Works & Engineering-PWE,Hispanic,Female,35485
1117 | Parks & Recreation,Black,Female,29682
1118 | Houston Police Department-HPD,Black,Female,55461
1119 | Houston Airport System (HAS),Hispanic,Male,73065
1120 | Houston Police Department-HPD,White,Female,66614
1121 | Houston Airport System (HAS),White,Male,49275
1122 | Public Works & Engineering-PWE,White,Male,49691
1123 | Public Works & Engineering-PWE,Black,Female,39208
1124 | Houston Airport System (HAS),White,Male,29286
1125 | Houston Fire Department (HFD),Black,Male,89590
1126 | Houston Police Department-HPD,Black,Male,52514
1127 | Houston Fire Department (HFD),Black,Male,55437
1128 | Parks & Recreation,White,Male,85055
1129 | Houston Police Department-HPD,Hispanic,Male,61643
1130 | Houston Fire Department (HFD),White,Male,62540
1131 | Houston Airport System (HAS),Black,Male,96325
1132 | Public Works & Engineering-PWE,Black,Female,35485
1133 | Houston Police Department-HPD,White,Male,66614
1134 | Health & Human Services,White,Female,33821
1135 | Public Works & Engineering-PWE,Hispanic,Female,60248
1136 | Public Works & Engineering-PWE,Black,Male,33987
1137 | Houston Fire Department (HFD),Hispanic,Female,41995
1138 | Public Works & Engineering-PWE,Hispanic,Male,32240
1139 | Houston Police Department-HPD,White,Male,55461
1140 | Houston Police Department-HPD,Hispanic,Male,55461
1141 | Houston Police Department-HPD,White,Male,60347
1142 | Parks & Recreation,Hispanic,Female,83916
1143 | Houston Airport System (HAS),Black,Female,39125
1144 | Houston Airport System (HAS),Asian,Male,77535
1145 | Houston Fire Department (HFD),White,Female,43014
1146 | Houston Fire Department (HFD),White,Male,70181
1147 | Public Works & Engineering-PWE,Hispanic,Male,36130
1148 | Houston Airport System (HAS),Hispanic,Male,57782
1149 | Houston Police Department-HPD,Asian,Male,55461
1150 | Houston Fire Department (HFD),White,Male,63166
1151 | Public Works & Engineering-PWE,White,Female,75224
1152 | Houston Fire Department (HFD),White,Male,66523
1153 | Houston Police Department-HPD,Hispanic,Male,66614
1154 | Houston Fire Department (HFD),White,Male,89590
1155 | Houston Police Department-HPD,White,Male,55461
1156 | Public Works & Engineering-PWE,Black,Male,65000
1157 | Houston Fire Department (HFD),White,Male,63166
1158 | Houston Police Department-HPD,Hispanic,Male,66614
1159 | Public Works & Engineering-PWE,Black,Female,74384
1160 | Houston Fire Department (HFD),White,Male,99480
1161 | Houston Fire Department (HFD),White,Male,48190
1162 | Houston Police Department-HPD,Black,Male,66614
1163 | Houston Police Department-HPD,White,Male,66614
1164 | Houston Police Department-HPD,Hispanic,Male,60347
1165 | Health & Human Services,Black,Male,54857
1166 | Houston Airport System (HAS),Black,Male,51501
1167 | Houston Fire Department (HFD),Hispanic,Male,43528
1168 | Houston Fire Department (HFD),White,Male,55437
1169 | Houston Police Department-HPD,Black,Female,91181
1170 | Houston Police Department-HPD,Black,Female,61643
1171 | Houston Fire Department (HFD),Black,Male,40170
1172 | Houston Fire Department (HFD),White,Male,89590
1173 | Houston Police Department-HPD,Black,Female,45677
1174 | Houston Police Department-HPD,Black,Female,35318
1175 | Parks & Recreation,Black,Female,40706
1176 | Health & Human Services,Black,Female,67782
1177 | Houston Fire Department (HFD),White,Male,66523
1178 | Houston Police Department-HPD,Black,Female,35318
1179 | Houston Police Department-HPD,White,Male,55461
1180 | Health & Human Services,Asian,Female,51861
1181 | Houston Police Department-HPD,White,Male,28169
1182 | Houston Fire Department (HFD),Black,Male,40170
1183 | Houston Police Department-HPD,Hispanic,Male,61643
1184 | Houston Fire Department (HFD),Hispanic,Male,62540
1185 | Houston Police Department-HPD,White,Male,77076
1186 | Houston Fire Department (HFD),Black,Male,74270
1187 | Houston Police Department-HPD,Hispanic,Female,26229
1188 | Houston Fire Department (HFD),White,Male,28024
1189 | Houston Police Department-HPD,Hispanic,Female,47650
1190 | Houston Police Department-HPD,White,Male,55461
1191 | Health & Human Services,Black,Female,47445
1192 | Houston Police Department-HPD,White,Male,43443
1193 | Houston Police Department-HPD,White,Male,81239
1194 | Houston Police Department-HPD,White,Male,55461
1195 | Houston Airport System (HAS),White,Male,39333
1196 | Health & Human Services,White,Female,36150
1197 | Public Works & Engineering-PWE,Black,Male,51397
1198 | Houston Police Department-HPD,White,Male,55461
1199 | Houston Police Department-HPD,White,Male,55461
1200 | Houston Fire Department (HFD),White,Male,70181
1201 | Houston Airport System (HAS),Hispanic,Female,26125
1202 | Houston Police Department-HPD,Hispanic,Male,81239
1203 | Houston Police Department-HPD,Black,Male,66614
1204 | Health & Human Services,Black,Female,42890
1205 | Houston Police Department-HPD,White,Female,81239
1206 | Houston Police Department-HPD,Black,Male,66614
1207 | Public Works & Engineering-PWE,Black,Male,26125
1208 | Houston Police Department-HPD,Hispanic,Male,28169
1209 | Houston Fire Department (HFD),Hispanic,Male,99480
1210 | Parks & Recreation,Asian,Male,48651
1211 | Houston Police Department-HPD,Asian,Male,55461
1212 | Houston Police Department-HPD,Native American,Male,55461
1213 | Public Works & Engineering-PWE,Hispanic,Male,61564
1214 | Houston Fire Department (HFD),Hispanic,Male,43528
1215 | Houston Police Department-HPD,Asian,Male,61643
1216 | Health & Human Services,Hispanic,Male,58406
1217 | Health & Human Services,Hispanic,Female,47445
1218 | Parks & Recreation,Black,Male,55462
1219 | Houston Fire Department (HFD),White,Male,62540
1220 | Houston Fire Department (HFD),White,Male,43528
1221 | Public Works & Engineering-PWE,Hispanic,Male,41184
1222 | Public Works & Engineering-PWE,Asian,Male,89955
1223 | Parks & Recreation,White,Male,57824
1224 | Health & Human Services,Black,Female,88361
1225 | Houston Police Department-HPD,Black,Male,55461
1226 | Houston Fire Department (HFD),White,Male,66523
1227 | Houston Police Department-HPD,White,Male,66614
1228 | Houston Airport System (HAS),Native American,Female,68299
1229 | Houston Airport System (HAS),White,Male,90832
1230 | Houston Police Department-HPD,Black,Female,66614
1231 | Houston Fire Department (HFD),Black,Male,51194
1232 | Houston Fire Department (HFD),White,Male,62540
1233 | Health & Human Services,Black,Female,30846
1234 | Houston Fire Department (HFD),Black,Male,61226
1235 | Public Works & Engineering-PWE,White,Female,52562
1236 | Houston Airport System (HAS),Black,Male,29286
1237 | Public Works & Engineering-PWE,White,Male,27352
1238 | Public Works & Engineering-PWE,Black,Male,60840
1239 | Houston Fire Department (HFD),Black,Male,63166
1240 | Houston Police Department-HPD,Black,Female,63551
1241 | Houston Fire Department (HFD),White,Male,70181
1242 | Houston Police Department-HPD,Black,Male,60347
1243 | Houston Police Department-HPD,White,Male,81239
1244 | Parks & Recreation,White,Male,46566
1245 | Houston Police Department-HPD,White,Male,66614
1246 | Houston Police Department-HPD,Hispanic,Male,81239
1247 | Public Works & Engineering-PWE,White,Female,66867
1248 | Houston Fire Department (HFD),White,Male,66523
1249 | Houston Fire Department (HFD),Black,Male,78355
1250 | Houston Police Department-HPD,Black,Female,91181
1251 | Houston Fire Department (HFD),Hispanic,Male,63166
1252 | Public Works & Engineering-PWE,Hispanic,Male,32136
1253 | Houston Fire Department (HFD),White,Male,63166
1254 | Houston Police Department-HPD,Hispanic,Male,66614
1255 | Houston Police Department-HPD,Black,Female,35318
1256 | Houston Police Department-HPD,Black,Male,66614
1257 | Houston Police Department-HPD,Black,Male,66614
1258 | Houston Airport System (HAS),Black,Male,45240
1259 | Houston Police Department-HPD,Hispanic,Male,81239
1260 | Houston Fire Department (HFD),Black,Male,57815
1261 | Public Works & Engineering-PWE,Asian,Male,53373
1262 | Houston Fire Department (HFD),Hispanic,Male,43528
1263 | Houston Fire Department (HFD),Hispanic,Male,51194
1264 | Public Works & Engineering-PWE,White,Male,98895
1265 | Houston Police Department-HPD,White,Female,55461
1266 | Houston Police Department-HPD,White,Male,66614
1267 | Houston Police Department-HPD,Hispanic,Male,66614
1268 | Houston Police Department-HPD,White,Male,81239
1269 | Health & Human Services,Black,Female,33675
1270 | Public Works & Engineering-PWE,Black,Female,36629
1271 | Houston Police Department-HPD,Hispanic,Male,47650
1272 | Houston Police Department-HPD,White,Male,61755
1273 | Houston Fire Department (HFD),White,Male,70181
1274 | Houston Police Department-HPD,Hispanic,Male,55461
1275 | Houston Fire Department (HFD),Hispanic,Male,70181
1276 | Health & Human Services,Asian,Male,54288
1277 | Houston Fire Department (HFD),White,Male,62540
1278 | Public Works & Engineering-PWE,White,Male,53123
1279 | Houston Police Department-HPD,White,Male,104455
1280 | Public Works & Engineering-PWE,White,Male,83734
1281 | Houston Airport System (HAS),Hispanic,Male,42099
1282 | Houston Fire Department (HFD),White,Male,40170
1283 | Houston Fire Department (HFD),White,Male,70181
1284 | Parks & Recreation,White,Male,26125
1285 | Houston Fire Department (HFD),White,Male,55437
1286 | Public Works & Engineering-PWE,Black,Male,52832
1287 | Parks & Recreation,Hispanic,Female,54607
1288 | Houston Police Department-HPD,Hispanic,Male,61643
1289 | Public Works & Engineering-PWE,White,Female,53061
1290 | Public Works & Engineering-PWE,Black,Male,45926
1291 | Houston Police Department-HPD,Black,Female,60347
1292 | Houston Police Department-HPD,White,Male,66614
1293 | Houston Police Department-HPD,Black,Male,66614
1294 | Public Works & Engineering-PWE,White,Male,57928
1295 | Houston Police Department-HPD,White,Male,42000
1296 | Houston Fire Department (HFD),White,Male,63166
1297 | Houston Fire Department (HFD),Black,Male,57815
1298 | Houston Police Department-HPD,White,Female,55411
1299 | Houston Police Department-HPD,Black,Male,55461
1300 | Public Works & Engineering-PWE,Black,Male,31450
1301 | Public Works & Engineering-PWE,White,Male,54600
1302 | Public Works & Engineering-PWE,Black,Male,33987
1303 | Houston Police Department-HPD,White,Male,45344
1304 | Public Works & Engineering-PWE,White,Male,51584
1305 | Houston Police Department-HPD,Black,Female,37336
1306 | Houston Police Department-HPD,White,Male,66614
1307 | Houston Fire Department (HFD),White,Male,52644
1308 | Public Works & Engineering-PWE,Black,Female,39270
1309 | Parks & Recreation,Black,Male,38147
1310 | Houston Fire Department (HFD),White,Male,62540
1311 | Houston Fire Department (HFD),White,Male,57815
1312 | Houston Police Department-HPD,Hispanic,Male,60347
1313 | Houston Police Department-HPD,Hispanic,Male,66614
1314 | Houston Fire Department (HFD),White,Male,78355
1315 | Public Works & Engineering-PWE,White,Male,52582
1316 | Houston Fire Department (HFD),Hispanic,Male,52644
1317 | Houston Airport System (HAS),Hispanic,Female,96157
1318 | Public Works & Engineering-PWE,White,Male,91063
1319 | Houston Police Department-HPD,White,Male,55461
1320 | Houston Police Department-HPD,Hispanic,Male,47650
1321 | Health & Human Services,Black,Female,36941
1322 | Public Works & Engineering-PWE,Black,Female,45240
1323 | Houston Airport System (HAS),Black,Female,37752
1324 | Houston Police Department-HPD,Hispanic,Male,81239
1325 | Houston Airport System (HAS),White,Male,103427
1326 | Parks & Recreation,Black,Female,30347
1327 | Public Works & Engineering-PWE,Black,Male,43326
1328 | Public Works & Engineering-PWE,Hispanic,Male,44658
1329 | Houston Fire Department (HFD),Black,Female,33405
1330 | Houston Fire Department (HFD),White,Male,66523
1331 | Public Works & Engineering-PWE,Black,Male,53373
1332 | Public Works & Engineering-PWE,Black,Male,31450
1333 | Houston Fire Department (HFD),White,Male,62540
1334 | Houston Police Department-HPD,Black,Male,81239
1335 | Public Works & Engineering-PWE,Black,Male,34320
1336 | Houston Police Department-HPD,White,Female,77076
1337 | Houston Airport System (HAS),Black,Female,33613
1338 | Houston Police Department-HPD,Hispanic,Female,47650
1339 | Houston Police Department-HPD,Asian,Male,47650
1340 | Public Works & Engineering-PWE,Black,Male,26125
1341 | Parks & Recreation,White,Male,29682
1342 | Houston Police Department-HPD,Hispanic,Male,55461
1343 | Houston Police Department-HPD,Black,Male,37024
1344 | Houston Fire Department (HFD),Hispanic,Male,55437
1345 | Houston Fire Department (HFD),White,Male,63166
1346 | Houston Police Department-HPD,Hispanic,Male,81239
1347 | Public Works & Engineering-PWE,Black,Male,73193
1348 | Houston Fire Department (HFD),Black,Male,51194
1349 | Public Works & Engineering-PWE,Black,Male,30139
1350 | Houston Police Department-HPD,Black,Male,61643
1351 | Houston Fire Department (HFD),White,Male,48190
1352 | Houston Fire Department (HFD),White,Male,89590
1353 | Houston Police Department-HPD,White,Male,66614
1354 | Houston Airport System (HAS),Hispanic,Male,35485
1355 | Houston Fire Department (HFD),Hispanic,Male,40170
1356 | Houston Police Department-HPD,White,Male,66614
1357 | Houston Police Department-HPD,White,Female,66614
1358 | Houston Police Department-HPD,White,Male,66614
1359 | Houston Police Department-HPD,Hispanic,Male,47650
1360 | Houston Fire Department (HFD),Hispanic,Male,74270
1361 | Public Works & Engineering-PWE,Asian,Male,45074
1362 | Public Works & Engineering-PWE,Black,Male,97581
1363 | Houston Fire Department (HFD),Hispanic,Male,52644
1364 | Public Works & Engineering-PWE,Black,Male,36130
1365 | Public Works & Engineering-PWE,Black,Female,29931
1366 | Houston Police Department-HPD,White,Male,81239
1367 | Health & Human Services,Black,Female,36358
1368 | Houston Fire Department (HFD),Black,Male,70181
1369 | Parks & Recreation,Black,Female,26125
1370 | Public Works & Engineering-PWE,Hispanic,Male,31158
1371 | Houston Police Department-HPD,Hispanic,Male,74511
1372 | Houston Police Department-HPD,White,Male,81239
1373 | Houston Fire Department (HFD),Black,Male,70181
1374 | Houston Police Department-HPD,Hispanic,Male,66614
1375 | Houston Police Department-HPD,Asian,Male,55461
1376 | Public Works & Engineering-PWE,Black,Female,29016
1377 | Houston Fire Department (HFD),Hispanic,Male,43528
1378 | Public Works & Engineering-PWE,Black,Male,26125
1379 | Houston Fire Department (HFD),White,Male,63166
1380 | Public Works & Engineering-PWE,Black,Male,49150
1381 | Houston Police Department-HPD,White,Male,55461
1382 | Houston Police Department-HPD,Black,Female,35318
1383 | Houston Police Department-HPD,Black,Female,63165
1384 | Public Works & Engineering-PWE,Black,Female,70717
1385 | Houston Police Department-HPD,Hispanic,Male,47650
1386 | Houston Fire Department (HFD),White,Female,55437
1387 | Health & Human Services,Black,Female,66178
1388 | Houston Fire Department (HFD),Hispanic,Male,70181
1389 | Houston Police Department-HPD,White,Male,66614
1390 | Public Works & Engineering-PWE,Hispanic,Female,52562
1391 | Houston Fire Department (HFD),White,Male,52644
1392 | Houston Police Department-HPD,White,Male,104455
1393 | Houston Police Department-HPD,Black,Male,61643
1394 | Public Works & Engineering-PWE,Black,Female,32178
1395 | Houston Police Department-HPD,White,Male,72332
1396 | Houston Police Department-HPD,White,Male,91181
1397 | Houston Police Department-HPD,Black,Male,55461
1398 | Houston Police Department-HPD,White,Male,55461
1399 | Houston Airport System (HAS),Hispanic,Male,40435
1400 | Houston Fire Department (HFD),White,Male,40170
1401 | Houston Fire Department (HFD),Black,Male,48190
1402 | Houston Fire Department (HFD),White,Male,89590
1403 | Houston Police Department-HPD,White,Male,55461
1404 | Houston Police Department-HPD,Hispanic,Male,61643
1405 | Public Works & Engineering-PWE,Asian,Male,114671
1406 | Public Works & Engineering-PWE,Black,Male,43264
1407 | Houston Police Department-HPD,Hispanic,Male,60347
1408 | Houston Police Department-HPD,White,Male,81239
1409 | Public Works & Engineering-PWE,Black,Male,49608
1410 | Houston Airport System (HAS),Asian,Male,32323
1411 | Houston Fire Department (HFD),White,Male,62540
1412 | Health & Human Services,Black,Female,83466
1413 | Public Works & Engineering-PWE,Black,Female,47008
1414 | Houston Fire Department (HFD),Hispanic,Male,28024
1415 | Houston Police Department-HPD,Hispanic,Male,66614
1416 | Health & Human Services,Hispanic,Female,26125
1417 | Public Works & Engineering-PWE,Asian,Male,47840
1418 | Health & Human Services,Black,Male,55806
1419 | Houston Airport System (HAS),Hispanic,Male,76097
1420 | Health & Human Services,Asian,Female,48672
1421 | Public Works & Engineering-PWE,Hispanic,Male,36629
1422 | Houston Police Department-HPD,Black,Male,43443
1423 | Public Works & Engineering-PWE,Hispanic,Male,28766
1424 | Houston Police Department-HPD,White,Male,81239
1425 | Houston Police Department-HPD,Hispanic,Male,55461
1426 | Public Works & Engineering-PWE,Black,Female,38750
1427 | Houston Fire Department (HFD),Hispanic,Male,66523
1428 | Houston Fire Department (HFD),Black,Male,63166
1429 | Public Works & Engineering-PWE,Black,Female,27352
1430 | Public Works & Engineering-PWE,Hispanic,Male,57200
1431 | Houston Fire Department (HFD),Hispanic,Male,70181
1432 | Houston Fire Department (HFD),Hispanic,Male,55437
1433 | Houston Fire Department (HFD),Hispanic,Male,61226
1434 | Public Works & Engineering-PWE,Black,Male,29557
1435 | Houston Fire Department (HFD),White,Male,78355
1436 | Houston Airport System (HAS),Hispanic,Male,115416
1437 | Houston Police Department-HPD,Black,Female,43722
1438 | Houston Fire Department (HFD),White,Male,61226
1439 | Public Works & Engineering-PWE,Black,Male,39666
1440 | Houston Police Department-HPD,Hispanic,Male,66614
1441 | Houston Airport System (HAS),Black,Male,29453
1442 | Health & Human Services,Black,Female,68432
1443 | Health & Human Services,Black,Male,47050
1444 | Houston Fire Department (HFD),Hispanic,Male,61921
1445 | Houston Police Department-HPD,Black,Male,66614
1446 | Houston Airport System (HAS),Hispanic,Female,32906
1447 | Houston Police Department-HPD,White,Female,30888
1448 | Public Works & Engineering-PWE,Black,Male,31158
1449 | Houston Police Department-HPD,Hispanic,Male,66614
1450 | Public Works & Engineering-PWE,White,Male,53123
1451 | Houston Fire Department (HFD),Black,Male,61226
1452 | Houston Fire Department (HFD),Black,Male,62540
1453 | Parks & Recreation,White,Male,29307
1454 | Health & Human Services,Hispanic,Female,27310
1455 | Public Works & Engineering-PWE,White,Female,83036
1456 | Houston Police Department-HPD,Hispanic,Male,81239
1457 | Houston Fire Department (HFD),Hispanic,Male,63166
1458 | Parks & Recreation,Hispanic,Male,47466
1459 | Houston Police Department-HPD,Black,Female,37211
1460 | Health & Human Services,Native American,Female,58855
1461 | Houston Fire Department (HFD),Black,Female,63166
1462 | Houston Fire Department (HFD),White,Male,61921
1463 | Health & Human Services,Black,Female,38563
1464 | Houston Police Department-HPD,White,Male,81239
1465 | Public Works & Engineering-PWE,Black,Male,83734
1466 | Houston Fire Department (HFD),Hispanic,Male,55437
1467 | Houston Fire Department (HFD),White,Male,48190
1468 | Houston Fire Department (HFD),White,Male,66523
1469 | Houston Airport System (HAS),White,Male,39707
1470 | Houston Airport System (HAS),Black,Male,75462
1471 | Houston Fire Department (HFD),White,Male,55437
1472 | Public Works & Engineering-PWE,Asian,Male,28995
1473 | Houston Police Department-HPD,Black,Male,42000
1474 | Public Works & Engineering-PWE,Hispanic,Male,39666
1475 | Health & Human Services,Black,Female,87222
1476 | Houston Police Department-HPD,Black,Female,47650
1477 | Houston Police Department-HPD,Black,Female,35838
1478 | Houston Airport System (HAS),Black,Male,26125
1479 | Houston Fire Department (HFD),White,Male,66523
1480 | Houston Police Department-HPD,Hispanic,Male,55461
1481 | Houston Police Department-HPD,Black,Male,81239
1482 | Houston Airport System (HAS),Black,Female,26125
1483 | Houston Police Department-HPD,Hispanic,Female,34216
1484 | Houston Fire Department (HFD),White,Male,66523
1485 | Houston Police Department-HPD,Black,Female,33550
1486 | Health & Human Services,Asian,Female,44366
1487 | Houston Police Department-HPD,White,Male,66614
1488 | Houston Police Department-HPD,Black,Female,26250
1489 | Houston Police Department-HPD,White,Male,55461
1490 | Public Works & Engineering-PWE,Black,Male,51584
1491 | Houston Police Department-HPD,Black,Female,66614
1492 | Public Works & Engineering-PWE,Black,Male,41267
1493 | Houston Airport System (HAS),White,Female,128606
1494 | Public Works & Engineering-PWE,Black,Female,93089
1495 | Houston Airport System (HAS),Hispanic,Male,43826
1496 | Public Works & Engineering-PWE,Black,Male,56489
1497 | Parks & Recreation,Hispanic,Female,41330
1498 | Houston Police Department-HPD,Black,Female,47650
1499 | Public Works & Engineering-PWE,White,Female,39666
1500 | Houston Police Department-HPD,White,Male,55461
1501 | Houston Police Department-HPD,Hispanic,Male,62481
1502 | Houston Police Department-HPD,White,Male,55461
1503 | Houston Police Department-HPD,Black,Female,37461
1504 | Public Works & Engineering-PWE,Black,Male,37606
1505 | Public Works & Engineering-PWE,Hispanic,Male,36962
1506 | Houston Police Department-HPD,White,Female,66614
1507 | Houston Fire Department (HFD),Black,Female,52000
1508 | Public Works & Engineering-PWE,Black,Male,43264
1509 | Houston Police Department-HPD,Asian,Male,55461
1510 | Houston Police Department-HPD,Black,Female,47996
1511 | Houston Police Department-HPD,White,Male,78286
1512 | Health & Human Services,White,Female,65338
1513 | Houston Police Department-HPD,Hispanic,Male,66614
1514 | Houston Police Department-HPD,Hispanic,Female,45279
1515 | Houston Fire Department (HFD),White,Male,63166
1516 | Public Works & Engineering-PWE,White,Male,77325
1517 | Houston Police Department-HPD,Black,Male,66614
1518 | Houston Airport System (HAS),Asian,Female,32157
1519 | Houston Police Department-HPD,White,Male,45279
1520 | Houston Police Department-HPD,Black,Female,27914
1521 | Houston Police Department-HPD,Hispanic,Male,66614
1522 | Houston Fire Department (HFD),Black,Male,70181
1523 | Houston Fire Department (HFD),Black,Male,55437
1524 | Houston Fire Department (HFD),White,Male,61921
1525 | Houston Police Department-HPD,White,Male,77076
1526 | Public Works & Engineering-PWE,Black,Male,37211
1527 | Parks & Recreation,Black,Female,30347
1528 | Houston Police Department-HPD,Hispanic,Female,44429
1529 | Houston Airport System (HAS),Hispanic,Male,29286
1530 | Houston Police Department-HPD,White,Male,81239
1531 | Houston Police Department-HPD,Black,Male,104455
1532 | Houston Police Department-HPD,White,Male,43443
1533 | Houston Fire Department (HFD),Black,Male,66523
1534 | Houston Police Department-HPD,White,Male,43443
1535 | Houston Police Department-HPD,Asian,Male,55461
1536 | Houston Fire Department (HFD),Hispanic,Male,51194
1537 |
--------------------------------------------------------------------------------