From 9ea14c32355f2e30f1c8bee6c86544afd6d3718b Mon Sep 17 00:00:00 2001 From: Howard Gehring Date: Tue, 1 May 2018 00:35:04 -0400 Subject: [PATCH] Dev (#26) * Whitespace * Whitespace * Whitespace * Documentation * applyIndexMap tested and implemented * Documentation and array style * Testing filter function * Renaming filter to array_filter * Testing and implementing appending arrays to a DataFrame using $df[] = [1, 2, 3]; * Coverage added for append * Coverage added for append * Coverage added for preg_replace * Coverage for numeric and integer types * Coverage for datetime types * Coverage for datetime types * Coverage for currency type * Coverage for accounting type * Renamed groupBy to unique and added coverage * Adding coverage for rename * Adding various coverage cases, implement offset/limit pagination for html output * Updating --- README.md | 4 +- src/DataFrameCore.php | 245 +++++++++++------- src/DataType.php | 18 ++ src/IO/HTML.php | 8 +- .../Core/CoreDataFrameSelectUnitTest.php | 1 - .../Core/CoreDataFrameTypesUnitTest.php | 104 ++++++++ .../DataFrame/Core/CoreDataFrameUnitTest.php | 193 +++++++++++++- tests/DataFrame/FWF/FWFDataFrameUnitTest.php | 4 +- .../DataFrame/HTML/HTMLDataFrameUnitTest.php | 32 +++ 9 files changed, 502 insertions(+), 107 deletions(-) create mode 100644 src/DataType.php create mode 100644 tests/DataFrame/Core/CoreDataFrameTypesUnitTest.php diff --git a/README.md b/README.md index 63a94ec..ae694d6 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ composer require archon/dataframe ```json { "require": { - "archon/dataframe": "1.0.0" + "archon/dataframe": "1.1.0" } } ``` ### Requirements - - PHP 5.5 or higher + - PHP 7.1 or higher - php_pdo_sqlite extension - php_mbstring extension diff --git a/src/DataFrameCore.php b/src/DataFrameCore.php index 4a67a91..7f94df2 100644 --- a/src/DataFrameCore.php +++ b/src/DataFrameCore.php @@ -74,6 +74,7 @@ public function getIndex($index) /** * Applies a user-defined function to each row of the DataFrame. The parameters of the function include the row * being iterated over, and optionally the index. ie: apply(function($el, $ix) { ... }) + * * @param Closure $f * @return DataFrameCore * @since 0.1.0 @@ -97,28 +98,61 @@ public function apply(Closure $f) /** * Apply new values to specific rows of the DataFrame using row index. - * applyByIndex([2=>'F',3=>'M','5'=>'F'], 'gender'); - * @param Array $values + * + * If column is supplied, will apply to column. + * If column is absent, will apply to row. + * + * By column: + * $df->applyIndexMap([ + * 2 => 'foo', + * 3 => function($old_value) { return $new_value; }, + * 5 => 'baz', + * ], 'a'); + * + * By row: + * $df->applyIndexMap([ + * 2 => function($old_row) { return $new_row; }, + * 3 => [ 'a' => 1, 'b' => 2, 'c' => 3 ], + * ]); + * + * @param array $map keys are row indices, values are static * @param $column * @return DataFrameCore * @since 0.1.0 */ - public function applyByIndex(array $values, $column) - { - $this->mustHaveColumn($column); - foreach($values as $index => $value){ - $this->data[$index][$column] = $value; - } - return $this; + public function applyIndexMap(array $map, $column = null) + { + return $this->apply(function(&$row, $i) use ($map, $column) { + if (isset($map[$i])) { + $value = $map[$i]; + + if (is_callable($value) && is_null($column)) { + $row = $value($row); + } else if (is_callable($value) && !is_null($column)) { + $row[$column] = $value($row[$column]); + } else if (is_array($value) && is_null($column)) { + $row = $value; + } else if ((is_string($value) || is_numeric($value) || is_bool($value)) && !is_null($column)) { + $row[$column] = $value; + } + } + + return $row; + }); } + /** * Filter DataFrame rows using user-defined function. The parameters of the function include the row - * being iterated over, and the index. ie: filter(function($row, $index) { ... }) + * being iterated over, and the index. + * + * ie: + * $df = $df->array_filter(function($row, $index) { ... }); + * * @param Closure $f - * @return DataFrameCore + * @return DataFrame * @since 0.1.0 */ - public function filter(Closure $f) + public function array_filter(Closure $f) { return DataFrame::fromArray(array_filter($this->data, $f, ARRAY_FILTER_USE_BOTH)); } @@ -145,10 +179,12 @@ public function query($sql, PDO $pdo = null) $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); if ($driver === 'sqlite') { $sqlColumns = implode(', ', $this->columns); + // @codeCoverageIgnoreStart } elseif ($driver === 'mysql') { $sqlColumns = implode(' VARCHAR(255), ', $this->columns) . ' VARCHAR(255)'; } else { throw new DataFrameException("{$driver} is not yet supported for DataFrame query."); + // @codeCoverageIgnoreEnd } $pdo->exec("DROP TABLE IF EXISTS dataframe;"); @@ -173,6 +209,7 @@ public function query($sql, PDO $pdo = null) /** * Assertion that the DataFrame must have the column specified. If not then an exception is thrown. + * * @param $columnName * @throws InvalidColumnException * @since 0.1.0 @@ -186,6 +223,7 @@ public function mustHaveColumn($columnName) /** * Returns a boolean of whether the specified column exists. + * * @param $columnName * @return bool * @since 0.1.0 @@ -201,6 +239,7 @@ public function hasColumn($columnName) /** * Adds a new column to the DataFrame. + * * @internal * @param $columnName * @since 0.1.0 @@ -212,19 +251,20 @@ private function addColumn($columnName) } } - /** + /** * Adds multiple columns to the DataFrame. + * * @internal - * @param $columnNames + * @param array $columnNames * @since 1.0.1 */ - - private function addColumns($columnNames) + private function addColumns(array $columnNames) { - foreach($columnNames as $columnName){ + foreach($columnNames as $columnName) { $this->addColumn($columnName); } } + /** * Renames specific column. * @@ -234,7 +274,8 @@ private function addColumns($columnNames) * @param $from * @param $to */ - public function renameColumn($from, $to) { + public function renameColumn($from, $to) + { $this->mustHaveColumn($from); foreach ($this as $i => $row) { @@ -245,6 +286,7 @@ public function renameColumn($from, $to) { } $key = array_search($from, $this->columns); + if(($key) !== false) { $this->columns[$key] = $to; } @@ -252,6 +294,7 @@ public function renameColumn($from, $to) { /** * Removes a column (and all associated data) from the DataFrame. + * * @param $columnName * @since 0.1.0 */ @@ -274,7 +317,8 @@ public function append(DataFrame $other) $columns = $this->columns; - foreach ($other as $row) { + // TODO: Strange bug occurs when $other is used as an Iterator here, have to use toArray() to bypass + foreach ($other->toArray() as $row) { $newRow = []; foreach ($columns as $column) { $newRow[$column] = $row[$column]; @@ -288,14 +332,16 @@ public function append(DataFrame $other) /** * Replaces all occurences within the DataFrame of regex $pattern with string $replacement + * * @param $pattern * @param $replacement + * @return DataFrameCore */ - public function pregReplace($pattern, $replacement) + public function preg_replace($pattern, $replacement) { - foreach($this->data as &$row) { - $row = preg_replace($pattern, $replacement, $row); - } + return $this->apply(function($row) use ($pattern, $replacement) { + return preg_replace($pattern, $replacement, $row); + }); } /** @@ -318,32 +364,26 @@ public function convertTypes(array $typeMap, $fromDateFormat = null, $toDateForm { foreach ($this as $i => $row) { foreach ($typeMap as $column => $type) { - if ($type == 'DECIMAL') { - $this->data[$i][$column] = $this->convertDecimal($row[$column]); - } elseif ($type == 'INT') { + if ($type === DataType::NUMERIC) { + $this->data[$i][$column] = $this->convertNumeric($row[$column]); + } elseif ($type === DataType::INTEGER) { $this->data[$i][$column] = $this->convertInt($row[$column]); - } elseif ($type == 'DATE') { - $this->data[$i][$column] = $this->convertDate($row[$column], $fromDateFormat, $toDateFormat); - } elseif ($type == 'CURRENCY') { + } elseif ($type === DataType::DATETIME) { + $this->data[$i][$column] = $this->convertDatetime($row[$column], $fromDateFormat, $toDateFormat); + } elseif ($type == DataType::CURRENCY) { $this->data[$i][$column] = $this->convertCurrency($row[$column]); - } elseif ($type == 'ACCOUNTING') { + } elseif ($type == DataType::ACCOUNTING) { $this->data[$i][$column] = $this->convertAccounting($row[$column]); } } } } - private function convertDecimal($value) + private function convertNumeric($value) { - $value = str_replace(['$', ',', ' '], '', $value); + if (is_numeric($value)) return $value; - if (substr($value, 1) == '.') { - $value = '0'.$value; - } - - if ($value == '0' || $value == '' || $value == '-0.00') { - return '0.00'; - } + $value = str_replace(['$', ',', ' '], '', $value); if (substr($value, -1) == '-') { $value = '-'.substr($value, 0, -1); @@ -355,55 +395,42 @@ private function convertDecimal($value) private function convertInt($value) { - if ($value === '') { - return '0'; - } + if (empty($value)) return 0; if (substr($value, -1) === '-') { $value = '-'.substr($value, 0, -1); } - return str_replace(',', '', $value); + $value = str_replace(['$', ',', ' '], '', $value); + + return intval(str_replace(',', '', $value)); } - private function convertDate($value, $fromFormat, $toFormat) + private function convertDatetime($value, $fromFormat, $toFormat) { - if ($value === '') { - return '0001-01-01'; + if (empty($value)) { + return DateTime::createFromFormat('Y-m-d', '0001-01-01')->format($toFormat); } - if (is_array($fromFormat)) { - $errorParsingDate = false; - $currentFormat = null; - - foreach ($fromFormat as $dateFormat) { - $currentFormat = $dateFormat; - $oldDateTime = DateTime::createFromFormat($dateFormat, $value); - if ($oldDateTime === false) { - $errorParsingDate = true; - continue; - } else { - $newDateString = $oldDateTime->format($toFormat); - return $newDateString; - } - } + if (!is_array($fromFormat)) { + $fromFormat = [ $fromFormat ]; + } - if ($errorParsingDate === true) { - throw new RuntimeException("Error parsing date string '{$value}' with date format {$currentFormat}"); - } + $dateFormatSnapshot = null; - } else { + foreach ($fromFormat as $dateFormat) { + $dateFormatSnapshot = $dateFormat; - $oldDateTime = DateTime::createFromFormat($fromFormat, $value); + $oldDateTime = DateTime::createFromFormat($dateFormat, $value); if ($oldDateTime === false) { - throw new RuntimeException("Error parsing date string '{$value}' with date format {$fromFormat}"); + continue; + } else { + $newDateString = $oldDateTime->format($toFormat); + return $newDateString; } - - $newDateString = $oldDateTime->format($toFormat); - return $newDateString; } - throw new RuntimeException("Error parsing date string: '{$value}' with date format: {$fromFormat}"); + throw new RuntimeException("Error parsing date string '{$value}' with date format {$dateFormatSnapshot}"); } private function convertCurrency($value) @@ -413,10 +440,13 @@ private function convertCurrency($value) $value[0] = ($value[0] == '' or $value[0] == '-') ? '0' : $value[0]; $value[1] = ($value[1] == '' or $value[1] == '0') ? '00' : $value[1]; + $value[0] = floatval($value[0]); $dollars = number_format($value[0]).'.'.$value[1]; if (substr($dollars, 0, 1) == '-') { - $dollars = '-$'.substr($dollars, 1); + $dollars = '-$' . ltrim($dollars, '-'); + } elseif (substr($dollars, -1) == '-') { + $dollars = '-$' . rtrim($dollars, '-'); } else { $dollars = '$'.$dollars; } @@ -431,42 +461,49 @@ private function convertAccounting($value) $value[0] = ($value[0] == '' or $value[0] == '-') ? '0' : $value[0]; $value[1] = ($value[1] == '' or $value[1] == '0') ? '00' : $value[1]; + $value[0] = floatval($value[0]); $dollars = number_format($value[0]) . '.' . $value[1]; if (substr($dollars, 0, 1) == '-') { - $dollars = '('.substr($dollars, 1).')'; + $dollars = '('.ltrim($dollars, '-').')'; + } elseif(substr($dollars, -1) == '-') { + $dollars = '('.rtrim($dollars, '-').')'; } return '$'.$dollars; } /** - * Will group data similar to a SQL group by. + * Returns unique values of given column(s) * * @param $columns * @return DataFrame */ - public function groupBy($columns) + public function unique($columns) { - $groupedData = []; + if (!is_array($columns)) { + $columns = [ $columns ]; + } + $groupedData = []; $uniqueColumns = []; foreach($this->data as $row) { - - if (is_array($columns)) { - $uniqueData = null; - foreach ($columns as $column) { - $uniqueData .= $row[$column]; - } - } else { - $uniqueData = $row[$columns]; + $uniqueKey = null; + foreach ($columns as $column) { + $uniqueKey .= $row[$column]; } - if (isset($uniqueColumns[$uniqueData])) { + if (isset($uniqueColumns[$uniqueKey])) { continue; } else { - $uniqueColumns[$uniqueData] = true; - $groupedData[] = $row; + $uniqueColumns[$uniqueKey] = true; + + $new_row = array(); + foreach ($columns as $column) { + $new_row[$column] = $row[$column]; + } + + $groupedData[] = $new_row; } } @@ -479,6 +516,7 @@ public function groupBy($columns) /** * Provides isset($df['column']) functionality. + * * @internal * @param mixed $columnName * @return bool @@ -499,6 +537,7 @@ public function offsetExists($columnName) * Allows user retrieve DataFrame subsets from a two-dimensional array by * simply requesting an element of the instantiated DataFrame. * ie: $fooDF = $df['foo']; + * * @internal * @param mixed $columnName * @return DataFrame @@ -531,6 +570,7 @@ public function offsetGet($columnName) * $df['foo'] = function ($foo) { return $foo + 1; }; * $df['foo'] = 'bar'; * $df[] = [['gender'=>'Female','name'=>'Luy'],['title'=>'Mr','name'=>'Noah']]; + * * @internal * @param mixed $targetColumn * @param mixed $rightHandSide @@ -552,6 +592,7 @@ public function offsetSet($targetColumn, $rightHandSide) * Allows user set DataFrame columns from a single-column DataFrame. * ie: * $df['bar'] = $df['foo']; + * * @internal * @param $targetColumn * @param DataFrame $df @@ -583,6 +624,7 @@ private function offsetSetDataFrame($targetColumn, DataFrame $df) * Allows user set DataFrame columns from a Closure. * ie: * $df['foo'] = function ($foo) { return $foo + 1; }; + * * @internal * @param $targetColumn * @param Closure $f @@ -599,7 +641,9 @@ private function offsetSetClosure($targetColumn, Closure $f) * Allows user set DataFrame columns from a variable and add new rows to Dataframe * ie: * $df['foo'] = 'bar'; - * $df[] = [['gender'=>'Female','name'=>'Luy'],['title'=>'Mr','name'=>'Noah']]; + * + * $df[] = [ 'foo' => 1, 'bar' => 2, 'baz' => 3 ]; + * * @internal * @param $targetColumn * @param $value @@ -607,22 +651,21 @@ private function offsetSetClosure($targetColumn, Closure $f) */ private function offsetSetValue($targetColumn, $value) { - if(trim($targetColumn!='')){ - $this->addColumn($targetColumn); - foreach ($this as $i => $row) { - $this->data[$i][$targetColumn] = $value; - } - }elseif(is_array($value)){ - foreach($value as $row){ - $this->addColumns(array_keys($row)); - $this->data[] = $row; - } + if (trim($targetColumn != '')) { + $this->addColumn($targetColumn); + foreach ($this as $i => $row) { + $this->data[$i][$targetColumn] = $value; + } + } elseif (is_array($value)) { + $this->addColumns(array_keys($value)); + $this->data[] = $value; } } /** * Allows user to remove columns from the DataFrame using unset. * ie: unset($df['column']) + * * @param mixed $offset * @throws InvalidColumnException * @since 0.1.0 @@ -648,6 +691,7 @@ public function offsetUnset($offset) /** * Return the current element + * * @link http://php.net/manual/en/iterator.current.php * @return mixed Can return any type. * @since 0.1.0 @@ -659,6 +703,7 @@ public function current() /** * Move forward to next element + * * @link http://php.net/manual/en/iterator.next.php * @return void Any returned value is ignored. * @since 0.1.0 @@ -670,6 +715,7 @@ public function next() /** * Return the key of the current element + * * @link http://php.net/manual/en/iterator.key.php * @return mixed scalar on success, or null on failure. * @since 0.1.0 @@ -681,6 +727,7 @@ public function key() /** * Checks if current position is valid + * * @link http://php.net/manual/en/iterator.valid.php * @return boolean The return value will be casted to boolean and then evaluated. * Returns true on success or false on failure. @@ -693,6 +740,7 @@ public function valid() /** * Rewind the Iterator to the first element + * * @link http://php.net/manual/en/iterator.rewind.php * @return void Any returned value is ignored. * @since 0.1.0 @@ -708,6 +756,7 @@ public function rewind() /** * Count elements of an object + * * @link http://php.net/manual/en/countable.count.php * @return int The custom count as an integer. * The return value is cast to an integer. diff --git a/src/DataType.php b/src/DataType.php new file mode 100644 index 0000000..fd690fc --- /dev/null +++ b/src/DataType.php @@ -0,0 +1,18 @@ + "'", 'datatable' => null, 'colorColumns' => null, - 'limit' => 5000 + 'limit' => 5000, + 'offset' => 0, ]; public function __construct(array $data) @@ -69,6 +70,7 @@ public function assembleTable(array $options) $colorColumnsOpt = $options['colorColumns']; $limitOpt = $options['limit']; + $offsetOpt = $options['offset']; $columns = current($data); $columns = array_keys($columns); @@ -89,6 +91,10 @@ public function assembleTable(array $options) $columns = $fnTRTH($columns); + if ($offsetOpt > 0 and $offsetOpt < count($data)) { + $data = array_slice($data, $offsetOpt); + } + if ($limitOpt > 0 and $limitOpt < count($data)) { $data = array_slice($data, 0, $limitOpt); } diff --git a/tests/DataFrame/Core/CoreDataFrameSelectUnitTest.php b/tests/DataFrame/Core/CoreDataFrameSelectUnitTest.php index 211b7ff..264596a 100644 --- a/tests/DataFrame/Core/CoreDataFrameSelectUnitTest.php +++ b/tests/DataFrame/Core/CoreDataFrameSelectUnitTest.php @@ -38,7 +38,6 @@ public function testDataFrameSelectUpdate() $df = $df->query("UPDATE dataframe SET a = c * 2;"); - print_r($df['a']->toArray()); $expected = [ ['a' => 6, 'b' => 2, 'c' => 3], ['a' => 12, 'b' => 5, 'c' => 6], diff --git a/tests/DataFrame/Core/CoreDataFrameTypesUnitTest.php b/tests/DataFrame/Core/CoreDataFrameTypesUnitTest.php new file mode 100644 index 0000000..c02a641 --- /dev/null +++ b/tests/DataFrame/Core/CoreDataFrameTypesUnitTest.php @@ -0,0 +1,104 @@ + 1, 'integer' => 3.14, ], + ['numeric' => -4, 'integer' => '$5.23', ], + ['numeric' => 7.23, 'integer' => '8x', ], + ['numeric' => .3, 'integer' => '8-', ], + ['numeric' => '$3,000,000.4', 'integer' => 'asdf', ], + ['numeric' => '3.0-', 'integer' => "$3,456,789.23" ], + ]); + + $df->convertTypes([ + 'numeric' => DataType::NUMERIC, + 'integer' => DataType::INTEGER, + ]); + + foreach ($df as $row) { + $this->assertTrue(is_numeric($row['numeric'])); + $this->assertTrue(is_integer($row['integer'])); + } + + $this->assertSame([ + [ 'numeric' => 1, 'integer' => 3, ], + [ 'numeric' => -4, 'integer' => 5, ], + [ 'numeric' => 7.23, 'integer' => 8, ], + [ 'numeric' => 0.3, 'integer' => -8, ], + [ 'numeric' => '3000000.4', 'integer' => 0, ], + [ 'numeric' => '-3.0', 'integer' => 3456789 ], + ], $df->toArray()); + } + + public function testConvertDateTime() { + $df = DataFrame::fromArray([ + [ 'datetime' => '12/03/1996' ], + [ 'datetime' => '03-2001-04' ], + [ 'datetime' => 'Jun 04 2010' ], + [ 'datetime' => '' ], + ]); + + $df->convertTypes([ + 'datetime' => DataType::DATETIME, + ], [ 'd/m/Y', 'd-Y-m', 'M d Y' ], 'Y-m-d'); + + $this->assertSame([ + [ 'datetime' => '1996-03-12' ], + [ 'datetime' => '2001-04-03' ], + [ 'datetime' => '2010-06-04' ], + [ 'datetime' => '0001-01-01' ], + ], $df->toArray()); + + $df->convertTypes([ + 'datetime' => DataType::DATETIME, + ], 'Y-m-d', 'M d Y'); + + $this->assertSame([ + [ 'datetime' => 'Mar 12 1996' ], + [ 'datetime' => 'Apr 03 2001' ], + [ 'datetime' => 'Jun 04 2010' ], + [ 'datetime' => 'Jan 01 0001' ], + ], $df->toArray()); + + $this->expectExceptionMessage("Error parsing date string 'Mar 12 1996' with date format Y-m-d"); + $df->convertTypes([ + 'datetime' => DataType::DATETIME, + ], 'Y-m-d', 'Y-m-d'); + + } + + public function testConvertCurrencyACCOUNTING() { + $df = DataFrame::fromArray([ + [ 'currency' => '1', 'accounting' => '1' ], + [ 'currency' => '-123456789', 'accounting' => '-123456789' ], + [ 'currency' => '', 'accounting' => '' ], + [ 'currency' => '123.45', 'accounting' => '123.45' ], + [ 'currency' => 'asdf', 'accounting' => 'asdf' ], + [ 'currency' => 'asdf.56-', 'accounting' => 'asdf.56-' ], + ]); + + $df->convertTypes([ + 'currency' => DataType::CURRENCY, + 'accounting' => DataType::ACCOUNTING, + ]); + + $this->assertSame([ + [ 'currency' => '$1.00', 'accounting' => '$1.00' ], + [ 'currency' => '-$123,456,789.00', 'accounting' => '$(123,456,789.00)' ], + [ 'currency' => '$0.00', 'accounting' => '$0.00' ], + [ 'currency' => '$123.45', 'accounting' => '$123.45' ], + [ 'currency' => '$0.00', 'accounting' => '$0.00' ], + [ 'currency' => '-$0.56', 'accounting' => '$(0.56)' ], + ], $df->toArray()); + + } + +} \ No newline at end of file diff --git a/tests/DataFrame/Core/CoreDataFrameUnitTest.php b/tests/DataFrame/Core/CoreDataFrameUnitTest.php index b06108a..f28bd0e 100644 --- a/tests/DataFrame/Core/CoreDataFrameUnitTest.php +++ b/tests/DataFrame/Core/CoreDataFrameUnitTest.php @@ -6,9 +6,7 @@ class CoreDataFrameUnitTest extends TestCase { - /** - * @var DataFrame - */ + /** @var DataFrame */ private $df; private $input = [ @@ -155,4 +153,193 @@ public function testIsset() $this->assertEquals(true, isset($this->df['a'])); $this->assertEquals(false, isset($this->df['foo'])); } + + public function testApplyIndexMapValues() + { + $df = $this->df; + + $df->applyIndexMap([ + 0 => 0, + 2 => 0, + ], 'a'); + + $this->assertEquals([ + ['a' => 0, 'b' => 2, 'c' => 3], + ['a' => 4, 'b' => 5, 'c' => 6], + ['a' => 0, 'b' => 8, 'c' => 9], + ], $df->toArray()); + } + + public function testApplyIndexMapFunction() + { + $df = $this->df; + + $df->applyIndexMap([ + 0 => function($row) { + $row['a'] = 10; + return $row; + }, + 2 => function($row) { + $row['c'] = 20; + return $row; + }, + ]); + + $this->assertEquals([ + ['a' => 10, 'b' => 2, 'c' => 3], + ['a' => 4, 'b' => 5, 'c' => 6], + ['a' => 7, 'b' => 8, 'c' => 20], + ], $df->toArray()); + } + + public function testApplyIndexMapValueFunction() + { + $df = $this->df; + + $my_function = function($value) { + if ($value < 4) { + return 0; + } else if ($value > 4) { + return 1; + } else { + return $value; + } + }; + + $df->applyIndexMap([ + 0 => $my_function, + 2 => $my_function, + ], 'a'); + + $this->assertEquals([ + ['a' => 0, 'b' => 2, 'c' => 3], + ['a' => 4, 'b' => 5, 'c' => 6], + ['a' => 1, 'b' => 8, 'c' => 9], + ], $df->toArray()); + } + + public function testApplyIndexMapArray() + { + $df = $this->df; + + $df->applyIndexMap([ + 1 => [ 'a' => 301, 'b' => 404, 'c' => 500 ], + ]); + + $this->assertEquals([ + ['a' => 1, 'b' => 2, 'c' => 3], + ['a' => 301, 'b' => 404, 'c' => 500], + ['a' => 7, 'b' => 8, 'c' => 9], + ], $df->toArray()); + } + + public function testFilter() + { + $df = $this->df; + + $df = $df->array_filter(function($row) { + return $row['a'] > 4 || $row['a'] < 4; + }); + + $this->assertEquals([ + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + [ 'a' => 7, 'b' => 8, 'c' => 9 ], + ], $df->toArray()); + } + + public function testOffsetSetValueArray() + { + $df = $this->df; + + $df[] = [ 'a' => 10, 'b' => 11, 'c' => 12 ]; + + $this->assertEquals([ + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + [ 'a' => 4, 'b' => 5, 'c' => 6 ], + [ 'a' => 7, 'b' => 8, 'c' => 9 ], + [ 'a' => 10, 'b' => 11, 'c' => 12 ], + ], $df->toArray()); + } + + public function testAppend() + { + $df1 = $this->df; + $df2 = $this->df; + + // Test that appending an array with less than count of 1 will simply return the original DataFrame + $this->assertSame( + $df1, + $df1->append(DataFrame::fromArray(array())) + ); + + $df1->append($df2); + + $this->assertEquals([ + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + [ 'a' => 4, 'b' => 5, 'c' => 6 ], + [ 'a' => 7, 'b' => 8, 'c' => 9 ], + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + [ 'a' => 4, 'b' => 5, 'c' => 6 ], + [ 'a' => 7, 'b' => 8, 'c' => 9 ], + ], $df1->toArray()); + } + + public function testPregReplace() + { + $df1 = $this->df; + + $df1->preg_replace('/[1-5]/', 'foo'); + + $this->assertEquals([ + [ 'a' => 'foo', 'b' => 'foo', 'c' => 'foo' ], + [ 'a' => 'foo', 'b' => 'foo', 'c' => 6 ], + [ 'a' => 7, 'b' => 8, 'c' => 9 ], + ], $df1->toArray()); + } + + public function testGroupBy() { + $df = DataFrame::fromArray([ + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + [ 'a' => 1, 'b' => 3, 'c' => 4 ], + [ 'a' => 2, 'b' => 4, 'c' => 5 ], + [ 'a' => 2, 'b' => 4, 'c' => 6 ], + [ 'a' => 3, 'b' => 5, 'c' => 7 ], + [ 'a' => 3, 'b' => 5, 'c' => 8 ], + ]); + + $this->assertSame([ + [ 'a' => 1 ], + [ 'a' => 2 ], + [ 'a' => 3 ], + ], $df->unique('a')->toArray()); + + $this->assertSame([ + [ 'a' => 1, 'b' => 2 ], + [ 'a' => 1, 'b' => 3 ], + [ 'a' => 2, 'b' => 4 ], + [ 'a' => 3, 'b' => 5 ], + ], $df->unique(['a', 'b'])->toArray()); + + $this->assertSame([ + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + [ 'a' => 1, 'b' => 3, 'c' => 4 ], + [ 'a' => 2, 'b' => 4, 'c' => 5 ], + [ 'a' => 2, 'b' => 4, 'c' => 6 ], + [ 'a' => 3, 'b' => 5, 'c' => 7 ], + [ 'a' => 3, 'b' => 5, 'c' => 8 ], + ], $df->unique(['a', 'b', 'c'])->toArray()); + } + + public function testRename() { + $df = $this->df; + + $df->renameColumn('a', 'foo'); + + $this->assertSame([ + ['foo' => 1, 'b' => 2, 'c' => 3], + ['foo' => 4, 'b' => 5, 'c' => 6], + ['foo' => 7, 'b' => 8, 'c' => 9], + ], $df->toArray()); + } + } diff --git a/tests/DataFrame/FWF/FWFDataFrameUnitTest.php b/tests/DataFrame/FWF/FWFDataFrameUnitTest.php index 7afd48d..093f7dc 100644 --- a/tests/DataFrame/FWF/FWFDataFrameUnitTest.php +++ b/tests/DataFrame/FWF/FWFDataFrameUnitTest.php @@ -20,10 +20,10 @@ public function testLoadFWF1() xyz opas 78-90-1234 9012 */ $df = DataFrame::fromFWF($fileName, [ - 'col1' => [0, 4], + 'col1' => ['*', 4], 'col2' => [7, 11], 'col3' => [14, 24], - 'col4' => [25, 29] + 'col4' => [25, '*'] ], ['include' => '/^.{14}\d{2}-\d{2}-\d{4}/']); $testArr = $df->toArray(); diff --git a/tests/DataFrame/HTML/HTMLDataFrameUnitTest.php b/tests/DataFrame/HTML/HTMLDataFrameUnitTest.php index d333b0d..51618c2 100644 --- a/tests/DataFrame/HTML/HTMLDataFrameUnitTest.php +++ b/tests/DataFrame/HTML/HTMLDataFrameUnitTest.php @@ -27,6 +27,38 @@ public function testToHTML() $this->assertEquals($expected, $df->toHTML()); } + public function testLimit() + { + $df = DataFrame::fromArray([ + ['a' => 1, 'b' => 2, 'c' => 3], + ['a' => 4, 'b' => 5, 'c' => 6], + ['a' => 7, 'b' => 8, 'c' => 9], + ['a' => 10, 'b' => 11, 'c' => 12], + ]); + + $expected = ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= "
abc
abc
123
456
"; + $this->assertEquals($expected, $df->toHTML([ 'limit' => 2 ])); + + $expected = ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= ""; + $expected .= "
abc
abc
789
101112
"; + $this->assertEquals($expected, $df->toHTML([ 'offset' => 2, 'limit' => 2 ])); + + + } + public function testPrettyToHTML() { $df = DataFrame::fromArray([