将一系列值映射到另一个范围

97 投票
10 回答
155979 浏览
提问于 2025-04-15 17:25

我在寻找一些想法,想知道如何在Python中把一个范围的数值转换成另一个范围的数值。我正在做一个硬件项目,读取一个传感器的数据,这个传感器可以返回一系列的数值,然后我用这些数据来驱动一个需要不同数值范围的执行器。

举个例子,假设传感器返回的数值范围是1到512,而执行器需要的数值范围是5到10。我想要一个函数,可以把一个数值和这两个范围传进去,然后返回一个映射到第二个范围的数值。如果这个函数叫做translate,那么可以这样使用:

sensor_value = 256
actuator_value = translate(sensor_value, 1, 512, 5, 10)

在这个例子中,我希望输出的actuator_value7.5,因为sensor_value正好在输入范围的中间。

10 个回答

30

其实这是一个很好的例子,可以用到闭包的概念,也就是写一个函数,然后这个函数再返回另一个函数。因为你可能有很多这样的数值,所以每次都去计算这些数值的范围和因素其实没什么意义,也不需要一直传递那些最小值和最大值。

所以,试试这样做:

def make_interpolater(left_min, left_max, right_min, right_max): 
    # Figure out how 'wide' each range is  
    leftSpan = left_max - left_min  
    rightSpan = right_max - right_min  

    # Compute the scale factor between left and right values 
    scaleFactor = float(rightSpan) / float(leftSpan) 

    # create interpolation function using pre-calculated scaleFactor
    def interp_fn(value):
        return right_min + (value-left_min)*scaleFactor

    return interp_fn

现在你可以这样写你的处理器:

# create function for doing interpolation of the desired
# ranges
scaler = make_interpolater(1, 512, 5, 10)

# receive list of raw values from sensor, assign to data_list

# now convert to scaled values using map 
scaled_data = map(scaler, data_list)

# or a list comprehension, if you prefer
scaled_data = [scaler(x) for x in data_list]
120

使用 scipy.interpolate.interp1d

你还可以用 scipy.interpolate 这个包来进行这样的转换(如果你不介意依赖 SciPy 的话):

>>> from scipy.interpolate import interp1d
>>> m = interp1d([1,512],[5,10])
>>> m(256)
array(7.4951076320939336)

或者可以把 0 维的 scipy 数组转换回普通的浮点数:

>>> float(m(256))
7.4951076320939336

你也可以很轻松地在一个命令中进行多次转换:

>>> m([100,200,300])
array([ 5.96868885,  6.94716243,  7.92563601])

作为额外的功能,你可以从一个范围映射到另一个范围,比如如果你想把 [1,128] 映射到 [1,10],把 [128,256] 映射到 [10,90],再把 [256,512] 映射到 [90,100],你可以这样做:

>>> m = interp1d([1,128,256,512],[1,10,90,100])
>>> float(m(400))
95.625

interp1d 创建的是分段线性插值对象(就像函数一样可以调用)。

使用 numpy.interp

正如 ~unutbu 所提到的,numpy.interp 也是一个选择(依赖性更少):

>>> from numpy import interp
>>> interp(256,[1,512],[5,10])
7.4951076320939336
120

一种解决方案是:

def translate(value, leftMin, leftMax, rightMin, rightMax):
    # Figure out how 'wide' each range is
    leftSpan = leftMax - leftMin
    rightSpan = rightMax - rightMin

    # Convert the left range into a 0-1 range (float)
    valueScaled = float(value - leftMin) / float(leftSpan)

    # Convert the 0-1 range into a value in the right range.
    return rightMin + (valueScaled * rightSpan)

你可以考虑用代数的方法来提高效率,不过这样可能会让代码变得不太容易理解。

撰写回答