lib/fat_table/column.rb in fat_table-0.5.4 vs lib/fat_table/column.rb in fat_table-0.5.5

- old
+ new

@@ -189,12 +189,15 @@ avg var pvar dev pdev any? all? none? one?) # :category: Aggregates - # Return the first non-nil item in the Column. Works with any Column type. + # Return the first non-nil item in the Column, or nil if all items are + # nil. Works with any Column type. def first + return nil if items.all?(&:nil?) + if type == 'String' items.reject(&:blank?).first else items.filter_to_type(type).first end @@ -202,33 +205,37 @@ # :category: Aggregates # Return the last non-nil item in the Column. Works with any Column type. def last + return nil if items.all?(&:nil?) + if type == 'String' items.reject(&:blank?).last else items.filter_to_type(type).last end end # :category: Aggregates - # Return a count of the non-nil items in the Column. Works with any Column - # type. + # Return a count of the non-nil items in the Column, or the size of the + # column if all items are nil. Works with any Column type. def count + return items.size if items.all?(&:nil?) + if type == 'String' items.reject(&:blank?).count.to_d else items.filter_to_type(type).count.to_d end end # :category: Aggregates - # Return the smallest non-nil, non-blank item in the Column. Works with - # numeric, string, and datetime Columns. + # Return the smallest non-nil, non-blank item in the Column, or nil if all + # items are nil. Works with numeric, string, and datetime Columns. def min only_with('min', 'NilClass', 'Numeric', 'String', 'DateTime') if type == 'String' items.reject(&:blank?).min else @@ -236,12 +243,12 @@ end end # :category: Aggregates - # Return the largest non-nil, non-blank item in the Column. Works with - # numeric, string, and datetime Columns. + # Return the largest non-nil, non-blank item in the Column, or nil if all + # items are nil. Works with numeric, string, and datetime Columns. def max only_with('max', 'NilClass', 'Numeric', 'String', 'DateTime') if type == 'String' items.reject(&:blank?).max else @@ -249,38 +256,45 @@ end end # :category: Aggregates - # Return a Range object for the smallest to largest value in the column. - # Works with numeric, string, and datetime Columns. + # Return a Range object for the smallest to largest value in the column, + # or nil if all items are nil. Works with numeric, string, and datetime + # Columns. def range only_with('range', 'NilClass', 'Numeric', 'String', 'DateTime') + return nil if items.all?(&:nil?) + Range.new(min, max) end # :category: Aggregates - # Return the sum of the non-nil items in the Column. Works with numeric and - # string Columns. For a string Column, it will return the concatenation of - # the non-nil items. + # Return the sum of the non-nil items in the Column, or 0 if all items are + # nil. Works with numeric and string Columns. For a string Column, it + # will return the concatenation of the non-nil items. def sum + return 0 if type == 'NilClass' || items.all?(&:nil?) + only_with('sum', 'Numeric', 'String') if type == 'String' items.reject(&:blank?).join(' ') else items.filter_to_type(type).sum end end # :category: Aggregates - # Return the average value of the non-nil items in the Column. Works with - # numeric and datetime Columns. For datetime Columns, it converts each date - # to its Julian day number, computes the average, and then converts the - # average back to a DateTime. + # Return the average value of the non-nil items in the Column, or 0 if all + # items are nil. Works with numeric and datetime Columns. For datetime + # Columns, it converts each date to its Julian day number, computes the + # average, and then converts the average back to a DateTime. def avg + return 0 if type == 'NilClass' || items.all?(&:nil?) + only_with('avg', 'DateTime', 'Numeric') itms = items.filter_to_type(type) size = itms.size.to_d if type == 'DateTime' avg_jd = itms.map(&:jd).sum / size @@ -291,15 +305,18 @@ end # :category: Aggregates # Return the sample variance (the unbiased estimator of the population - # variance using a divisor of N-1) as the average squared deviation from the - # mean, of the non-nil items in the Column. Works with numeric and datetime - # Columns. For datetime Columns, it converts each date to its Julian day - # number and computes the variance of those numbers. + # variance using a divisor of N-1) as the average squared deviation from + # the mean, of the non-nil items in the Column, or 0 if all items are + # nil. Works with numeric and datetime Columns. For datetime Columns, it + # converts each date to its Julian day number and computes the variance of + # those numbers. def var + return 0 if type == 'NilClass' || items.all?(&:nil?) + only_with('var', 'DateTime', 'Numeric') all_items = if type == 'DateTime' items.filter_to_type(type).map(&:jd) else @@ -317,86 +334,103 @@ # :category: Aggregates # Return the population variance (the biased estimator of the population # variance using a divisor of N) as the average squared deviation from the - # mean, of the non-nil items in the Column. Works with numeric and datetime - # Columns. For datetime Columns, it converts each date to its Julian day - # number and computes the variance of those numbers. + # mean, of the non-nil items in the Column, or 0 if all items are + # nil. Works with numeric and datetime Columns. For datetime Columns, it + # converts each date to its Julian day number and computes the variance of + # those numbers. def pvar + return 0 if type == 'NilClass' || items.all?(&:nil?) + only_with('var', 'DateTime', 'Numeric') n = items.filter_to_type(type).size.to_d return BigDecimal('0.0') if n <= 1 var * ((n - 1) / n) end # :category: Aggregates # Return the sample standard deviation (the unbiased estimator of the # population standard deviation using a divisor of N-1) as the square root - # of the sample variance, of the non-nil items in the Column. Works with - # numeric and datetime Columns. For datetime Columns, it converts each date - # to its Julian day number and computes the standard deviation of those - # numbers. + # of the sample variance, of the non-nil items in the Column, or 0 if all + # items are nil. Works with numeric and datetime Columns. For datetime + # Columns, it converts each date to its Julian day number and computes the + # standard deviation of those numbers. def dev + return 0 if type == 'NilClass' || items.all?(&:nil?) + only_with('dev', 'DateTime', 'Numeric') var.sqrt(20) end # :category: Aggregates # Return the population standard deviation (the biased estimator of the - # population standard deviation using a divisor of N) as the square root of - # the population variance, of the non-nil items in the Column. Works with - # numeric and datetime Columns. For datetime Columns, it converts each date - # to its Julian day number and computes the standard deviation of those - # numbers. + # population standard deviation using a divisor of N) as the square root + # of the population variance, of the non-nil items in the Column, or 0 if + # all items are nil. Works with numeric and datetime Columns. For datetime + # Columns, it converts each date to its Julian day number and computes the + # standard deviation of those numbers. def pdev + return 0 if type == 'NilClass' || items.all?(&:nil?) + only_with('dev', 'DateTime', 'Numeric') Math.sqrt(pvar) end # :category: Aggregates # Return true if any of the items in the Column are true; otherwise return - # false. Works only with boolean Columns. + # false, or false if all items are nil. Works only with boolean Columns. def any? + return false if type == 'NilClass' || items.all?(&:nil?) + only_with('any?', 'Boolean') items.filter_to_type(type).any? end # :category: Aggregates # Return true if all of the items in the Column are true; otherwise return - # false. Works only with boolean Columns. + # false, or false if all items are nil. Works only with boolean Columns. def all? + return false if type == 'NilClass' || items.all?(&:nil?) + only_with('all?', 'Boolean') items.filter_to_type(type).all? end # :category: Aggregates - # Return true if none of the items in the Column are true; otherwise return - # false. Works only with boolean Columns. + # Return true if none of the items in the Column are true; otherwise + # return false, or true if all items are nil. Works only with boolean + # Columns. def none? + return true if type == 'NilClass' || items.all?(&:nil?) + only_with('none?', 'Boolean') items.filter_to_type(type).none? end # :category: Aggregates # Return true if precisely one of the items in the Column is true; # otherwise return false. Works only with boolean Columns. def one? + return false if type == 'NilClass' || items.all?(&:nil?) + only_with('one?', 'Boolean') items.filter_to_type(type).one? end private def only_with(agg, *valid_types) return self if valid_types.include?(type) + msg = "aggregate '#{agg}' cannot be applied to a #{type} column" raise UserError, msg end public @@ -434,10 +468,15 @@ end private def convert_and_set_type(val) - new_val = Convert.convert_to_type(val, type, tolerant: tolerant?) + begin + new_val = Convert.convert_to_type(val, type, tolerant: tolerant?) + rescue IncompatibleTypeError + err_msg = "attempt to add '#{val}' to column '#{header}' already typed as #{type}" + raise IncompatibleTypeError, err_msg + end if new_val && (type == 'NilClass' || type == 'String') @type = if [true, false].include?(new_val) 'Boolean' elsif new_val.is_a?(Date) || new_val.is_a?(DateTime)