将一系列值映射到另一个范围
我在寻找一些想法,想知道如何在Python中把一个范围的数值转换成另一个范围的数值。我正在做一个硬件项目,读取一个传感器的数据,这个传感器可以返回一系列的数值,然后我用这些数据来驱动一个需要不同数值范围的执行器。
举个例子,假设传感器返回的数值范围是1到512,而执行器需要的数值范围是5到10。我想要一个函数,可以把一个数值和这两个范围传进去,然后返回一个映射到第二个范围的数值。如果这个函数叫做translate
,那么可以这样使用:
sensor_value = 256
actuator_value = translate(sensor_value, 1, 512, 5, 10)
在这个例子中,我希望输出的actuator_value
是7.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)
你可以考虑用代数的方法来提高效率,不过这样可能会让代码变得不太容易理解。