# https://www.kaggle.com/arjanso/reducing-dataframe-memory-size-by-65

import numpy as np  # linear algebra
import pandas as pd  # data processing, CSV file I/O (e.g. pd.read_csv)


def reduce_mem_usage(props, nan_fill=-1):
    start_mem_usg = props.memory_usage().sum() / 1024**2
    print("DF Memory Before  : {:15.2f} MB".format(start_mem_usg))
    NAlist = []  # Keeps track of columns that have missing values filled in.
    for col in props.columns:
        if any([x for x in ['int', 'float'] if x in str(props[col].dtype)]):  # Only numeric
            # make variables for Int, max and min
            IsInt = False
            mx = props[col].max()
            mn = props[col].min()

            # Integer does not support NA, therefore, NA needs to be filled
            if not np.isfinite(props[col]).all():
                NAlist.append(col)
                props[col].fillna(nan_fill, inplace=True)

            # test if column can be converted to an integer
            asint = props[col].fillna(0).astype(np.int64)
            result = (props[col] - asint)
            result = result.sum()
            if result > -0.01 and result < 0.01:
                IsInt = True

            # Make Integer/unsigned Integer datatypes
            if IsInt:
                if mn >= 0:
                    if mx < 255:
                        props[col] = props[col].astype(np.uint8)
                    elif mx < 65535:
                        props[col] = props[col].astype(np.uint16)
                    elif mx < 4294967295:
                        props[col] = props[col].astype(np.uint32)
                    else:
                        props[col] = props[col].astype(np.uint64)
                else:
                    if mn > np.iinfo(np.int8).min and mx < np.iinfo(np.int8).max:
                        props[col] = props[col].astype(np.int8)
                    elif mn > np.iinfo(np.int16).min and mx < np.iinfo(np.int16).max:
                        props[col] = props[col].astype(np.int16)
                    elif mn > np.iinfo(np.int32).min and mx < np.iinfo(np.int32).max:
                        props[col] = props[col].astype(np.int32)
                    elif mn > np.iinfo(np.int64).min and mx < np.iinfo(np.int64).max:
                        props[col] = props[col].astype(np.int64)

            # Make float datatypes 32 bit
            else:
                props[col] = props[col].astype(np.float32)

    # Print final result
    mem_usg = props.memory_usage().sum() / 1024**2
    memory_reduce = 1-(mem_usg/start_mem_usg)
    print("DF Memory After   : {:15.2f} MB".format(mem_usg))
    print("Reduced by        : {:15.2f} %".format(100*memory_reduce))

    print("\nNumeric Columns with NAN replaced with {}:\n{}".format(nan_fill, NAlist))
    return props, NAlist
