def _round(x, y): # Python 3 uses "banker's rounding": when a value is equidistant between # two rounded numbers, it rounds to the even digit. For example # >>> round(2.5) # 2 # >>> round(3.5) # 4 # >>> round(2.25, 2) # 2.2 # # So, I've reimplemented the rounding most people expect. # # Either way, imprecision effects come into play. With either rounding, # one would expect round(2.675, 2) to be 2.68, but 2.675 is stored as # something slightly smaller than 2.675, so it rounds down to 2.67. # # Stata has this latter issue as well, depending on use. If you use # round(2.675, 0.01) interactively, you'll get 2.68. If you use # round(x, 0.01) where x is a variable containing 2.675 (approx), then # you get 2.67. If you use round(x, y) where x is a variable containing # 2.675 (approx) and y is a variable containing 0.01 (approx), then you # get 2.68 (approx). # # . di round(2.675, 0.01) # 2.68 # # . clear # # . set obs 1 # obs was 0, now 1 # # . gen x = 2.675 # # . di round(x, 0.01) # 2.67 # # . clear # # . set obs 1 # obs was 0, now 1 # # . gen x = 2.675 # # . gen y = 0.01 # # . di round(x, y) # 2.6799999 # if _is_missing(y): return mv if y == 0: if _is_missing(x) and not isinstance(x, MissingValue): return get_missing(x) return x if _is_missing(x): return x if isinstance(x, MissingValue) else get_missing(x) return math.floor(x / y + 0.5) * y
def _reldif(x, y): missing = False if x is None or (not isinstance(x, MissingValue) and (x < -8.988465674311579e+307 or x > 8.988465674311579e+307)): x = get_missing(x) missing = True if y is None or (not isinstance(y, MissingValue) and (y < -8.988465674311579e+307 or y > 8.988465674311579e+307)): y = get_missing(y) missing = True if x == y: return 0 if missing: return mv return abs(x - y) / (abs(y) + 1)
def _int(x): if isinstance(x, MissingValue): return x if x is None: return mv if not isinstance(x, int) and not isinstance(x, float): raise TypeError("int, float, or MissingValue instance required") if not -8.988465674311579e+307 <= x <= 8.988465674311579e+307: return get_missing(x) return int(x)
def st_round(x, y=1): """Rounding function. Parameters ---------- x : float, int, MissingValue instance, or None y : float, int, MissingValue instance, or None; y is optional, default value is 1 Returns ------- If both x and y are non-missing, returns x / y rounded to the nearest integer times y (but see notes below). If y is 1 or y is not specified, returns x rounded to the nearest integer (but see notes below). If y is zero, returns x. If y is missing, MISSING (".") is returned. If x is missing and y is non-missing, returns MissingValue corresponding to x. If both x and y are missing, returns MISSING ("."). Notes ----- Though Python 3 uses "banker's rounding" or "round half to even", this function uses "round half up". For example, with Python 3's `round` function, `round(3.5)` and `round(4.5)` are both 4, but `st_round(3.5)` is 4 and `st_round(4.5)` is 5. Keep in mind that floating point imprecision of inputs may affect the output. """ if isinstance(x, StataVarVals): if isinstance(y, StataVarVals): return StataVarVals([ _round(vx, vy) for vx, vy in zip(x.values, y.values) ]) if y == 0: return StataVarVals([ get_missing(v) if _is_missing(v) and not isinstance(v, MissingValue) else v for v in x.values ]) else: return StataVarVals([ _round(v, y) for v in x.values ]) if isinstance(y, StataVarVals): return StataVarVals([ _round(x, v) for v in y.values ]) return _round(x, y)
def st_round(x, y=1): """Rounding function. Parameters ---------- x : float, int, MissingValue instance, or None y : float, int, MissingValue instance, or None; y is optional, default value is 1 Returns ------- If both x and y are non-missing, returns x / y rounded to the nearest integer times y (but see notes below). If y is 1 or y is not specified, returns x rounded to the nearest integer (but see notes below). If y is zero, returns x. If y is missing, MISSING (".") is returned. If x is missing and y is non-missing, returns MissingValue corresponding to x. If both x and y are missing, returns MISSING ("."). Notes ----- Though Python 3 uses "banker's rounding" or "round half to even", this function uses "round half up". For example, with Python 3's `round` function, `round(3.5)` and `round(4.5)` are both 4, but `st_round(3.5)` is 4 and `st_round(4.5)` is 5. Keep in mind that floating point imprecision of inputs may affect the output. """ if isinstance(x, StataVarVals): if isinstance(y, StataVarVals): return StataVarVals( [_round(vx, vy) for vx, vy in zip(x.values, y.values)]) if y == 0: return StataVarVals([ get_missing(v) if _is_missing(v) and not isinstance(v, MissingValue) else v for v in x.values ]) else: return StataVarVals([_round(v, y) for v in x.values]) if isinstance(y, StataVarVals): return StataVarVals([_round(x, v) for v in y.values]) return _round(x, y)