程序猿成长之路之数据挖掘篇——距离公式介绍

上一篇介绍了朴素贝叶斯,那么这次讲讲距离公式

什么是距离公式

用自己的话来说距离公式就是判断两个属性(参数)相似度的度量公式,比如以两点间距离为例,A地经纬度为(110.9802,120.9932),B地经纬度为(110.9980,120.8280), C地经纬度为(98.0232,110.9829),那么我们可以得出|AB| < |AC|,也就是说A地离B地的距离更近。再举个例子,同一个学校里有不同的学生,学生又有性别、年龄、兴趣爱好等属性,那么如果两个学生之间的性别、年龄、兴趣爱好等属性越接近,那么这两个学生的距离公式所得出的结果也就越小,两个学生也就越相像。

为什么要有距离公式

距离公式存在的意义在于不仅可以计算两个数值属性的相似度(如同上述的经纬度信息),而且可以计算两个度量甚至标称属性的相似度。
* 数值、度量、标称、二元属性的定义详见: 数据挖掘介绍https://blog.csdn.net/qq_31236027/article/details/137046475
而计算相似度的意义在于之后可以区分不同对象的差异性,从而方便后续的挖掘关联规则并进行相似度推荐。

常见的距离计算方式(后续会拓展展开讲讲)

数值属性的距离计算公式:

  1. 欧几里得距离公式
  2. 曼哈顿距离公式
  3. 上确界距离(切比雪夫距离)公式
  4. 闵可夫斯基距离公式

序数属性的距离计算方式:
若设置Rif 表示第i个对象的第f个属性的值,且该属性为序数属性,该属性有n个可选的值,且每个值的权重相同,又设置Zif为序数距离计算后的值,则有:
Zif = (Rif - 1) / n

标称属性的距离计算公式:
d(i,j) = (p-m) / p ⇒ 个人理解为不同的属性占全部属性的比例。

二元属性的距离计算公式:

  1. Jaccard公式:sim(i,j) = q / q + r + s, d(i,j) = 1 - sim(i,j)
  2. 余弦相似度公式

混合属性的距离计算公式:
sim(i,j) = Σ(f=1, p) δij(f) * sim(i,j) (f) / Σ(f=1,p) δij(f)
怎么理解呢?后续会详细展开讲讲

欧几里得距离公式

假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = |AB| = (√该符号表示根号) √(x1-x2)^2 + (y1-y2)^2
也就是取不同数值属性的差的平方和再进行开根运算。得出的结果就是欧几里得距离公式得出的结果

曼哈顿距离公式

假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = |AB| = |x1-x2| + |y1 -y2|
也就是取不同数值属性的差的绝对值和。得出的结果就是曼哈顿距离得出的结果

闵可夫斯基距离公式

假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = √(h) |x1- x2|^h + |y1-y2|^h = (Σ(f=1,2) |xAf- xBf| ^h)^1/h
也就是取不同数值属性的差的h次方求和再进行开h次方根运算。得出的结果就是闵可
夫斯基距离公式得出的结果

上确界距离(切比雪夫距离)公式

假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = limh->∞((Σ(f=1,2) |xAf- xBf| ^h)^1/h = max(f=1, p) |xAf- XBf|
也就是取闵可夫斯基距离公式的极限,约等于差异最大的属性的差值。
上确界距离公式推导可见:https://blog.csdn.net/qq_31236027/article/details/106763491

序数属性的距离计算

例如:现在有一个属性叫成绩,其中有3个值,分别为好,一般,差,那么该属性可以进行序数化,也就是用数字代替文本求距离。(假设权重一致)
P(好) = (3-1)/(3-1) = 1, #前一个(3-1) 表示的是值的位置,后一个(3-1)是为了将值映射到[0.0,1.0]上
P(一般) = (2-1)/(3-1) = 0.5
P(差) = (1-1) / (3-1) = 0
之后计算距离就方便了,可以利用数值属性的距离计算公式进行计算。

标称属性的距离计算

标称属性计算就很简单,求出不相同的属性的数量去除以对象A和B之间所有标称属性
数量就是标称属性的距离。

二元属性的距离计算

Jaccard距离计算公式:
假设对象i属性值为1且对象j属性值也为1的二元属性数量为q,对象i属性值为0但对象j属性值为1的二元属性数量为s,对象i属性值为1但是对象j属性值为0的二元属性数量为r,对象i和对象j属性值均为0的属性数量为t,那么有:
在这里插入图片描述
sim(i,j) = q(对象i和对象j属性的值都为1) + t 对象i和对象j属性的值都为0) / (q+r+s+t)
又有:对象i和对象j属性值均为0的属性没有意义,所以上述公式可以优化成以下公式:
sim(i,j) = q / q + r + s = 1- d(i,j) #该公式为jaccard公式。

余弦相似度公式:
sim(i,j) = |A ∩ B| / |A ∪ B|

混合属性的距离计算

sim(i,j) = Σ(f=1, p) δij(f) * sim(i,j) (f) / Σ(f=1,p) δij(f)
就是可以简单理解为标称、序数、数值、二元属性的距离计算完成后进行求和之后取平均值,

相关代码

各位小伙伴可以借鉴以下,以下是距离公式的java实现

package diffUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 距离公式
 * @author zygswo
 *
 */
public class DiffUtils {
	
	/**
	 * 测试对象
	 * @author zygswo
	 *
	 */
	class TestObject {
		@Override
		public String toString() {
			return "TestObject [name=" + name + ", age=" + age + ", gender=" + gender + ", myHobby=" + myHobby
					+ ", myDream=" + myDream + "]";
		}
		public List<MyHobby> getMyHobby() {
			return myHobby;
		}
		public TestObject setMyHobby(List<MyHobby> myHobby) {
			this.myHobby = myHobby;
			return this;
		}
		public String getName() {
			return name;
		}
		public TestObject setName(String name) {
			this.name = name;
			return this;
		}
		public int getAge() {
			return age;
		}
		public TestObject setAge(int age) {
			this.age = age;
			return this;
		}
		public String getGender() {
			return gender;
		}
		public TestObject setGender(String gender) {
			this.gender = gender;
			return this;
		}
		String name;
		
		@Elem(weight=0.1,type = ElemType.NUMBER)
		int age;
		
		@Elem(weight=0.2,type = ElemType.XUSHU,list={"男","女"})
		String gender;
		
		@Elem(weight=0.3)
		List<MyHobby> myHobby;
		
		@Elem(weight=0.4)
		List<String> myDream;
		
		public TestObject(String name, int age, String gender) {
			super();
			this.name = name;
			this.age = age;
			this.gender = gender;
		}
		
		public TestObject(String name, int age, String gender,List<MyHobby> myHobby) {
			this(name,age,gender);
			this.myHobby = myHobby;
		}
		
		public TestObject(String name, int age, String gender,List<MyHobby> myHobby, List<String> myDreams) {
			this(name,age,gender);
			this.myHobby = myHobby;
			this.myDream = myDreams;
		}
		
		
	}
	
	/**
	 * 欧几里得距离公式
	 * @param obj1
	 * @param obj2
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	public static <T> double EuclidDistance(T obj1, T obj2) 
			throws IllegalArgumentException, IllegalAccessException {
		Class<?> cls = obj1.getClass();
		Field[] fs = cls.getDeclaredFields();
		double result = 0.0;
		for (Field f:fs) {
			f.setAccessible(true);
			Object xVal = f.get(obj1);
			Object yVal = f.get(obj2);
			long x = xVal.hashCode();
			long y = yVal.hashCode();
			System.out.println("x = " + x);
			System.out.println("y = " + y);
			result += EuclidDistance(x,y,1.0);
			System.out.println("result = " + result);
		}
		return Math.sqrt(result);
	}
	
	/**
	 * 计算相似度
	 * @param obj1
	 * @param obj2
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	public static <T> T calculDiff(T obj1, T ... obj2) 
			throws IllegalArgumentException, IllegalAccessException {
		double min = 1.0;
		T mostLikelyItem = null;
		for (T obj : obj2) {
			double res = Double.parseDouble(calculDiff(obj1, obj));
			System.out.println("res = " + res);
			if (res < min) {
				min = res;
				mostLikelyItem = obj;
			}
		}
		return mostLikelyItem;
	}
	
	/**
	 * 计算相似度
	 * @param obj1
	 * @param obj2
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	@SuppressWarnings("rawtypes")
	public static <T> String calculDiff(T obj1, T obj2) 
			throws IllegalArgumentException, IllegalAccessException {
		Class<?> cls = obj1.getClass();
		Class<?> cls2 = obj2.getClass();
		if (!cls.getName().equalsIgnoreCase(cls2.getName())) {
			throw new IllegalArgumentException("参数类型不匹配");
		}
		Field[] fs = cls.getDeclaredFields();
		double result = 0.0, restWeight = 1.0; //result=相似度,restWeight=剩余权重
		int blankWeightFieldCount = 0; //elem中权重为空的field数量
		//计算变量数量
		for (Field f0:fs) {
			Elem elem0 = f0.getAnnotation(Elem.class);
			if (elem0 != null) {
				if (elem0.weight() == 1) {
					blankWeightFieldCount++;
				} else {
					restWeight = restWeight - elem0.weight();
					restWeight = Double.parseDouble(
							String.format("%.2f",restWeight)
					);
				}
				if (restWeight < 0) {
					throw new IllegalArgumentException("权重总量分配超过1");
				}
			}
		}
		for (Field f:fs) {
			f.setAccessible(true);
			Object xVal = f.get(obj1);
			Object yVal = f.get(obj2);
			Elem elem = f.getAnnotation(Elem.class);
			if (elem == null) {
				continue;
			}
			if (xVal instanceof ArrayList && yVal instanceof ArrayList) {
				Object[] objList1 = ((ArrayList)xVal).toArray();
				Object[] objList2 = ((ArrayList)yVal).toArray();
				Object[] objList3 = new Object[objList1.length + objList2.length];
				/**
				 * 1. 数组复制
				 * 2. 数组排序
				 * 3. 利用jaccard公式计算相似度(相异性 为 1- 相似度)
				 */
				//数组进行复制
				System.arraycopy(objList1, 0, objList3, 0, objList1.length);
				System.arraycopy(objList2, 0, objList3, objList1.length, objList2.length);
				//数组排序
				Arrays.sort(objList3);
				int same = 0,total = objList3.length;
				for (int i = 0; i < objList3.length-1;i++) {
					Object pre = objList3[i];
					Object after = objList3[i+1];
					if (pre.equals(after)) {
						same++;		//重复的元素++
						total--;	//两个重复了,减去一个
						i++;
					}
				}
				//简单规则,jaccard公式计算相似度(相异性 为 1- 相似度)
				System.out.println("same = "  + same);
				System.out.println("total = "  + total);
				if (total != 0) {
					result += elem.weight() * (1 - same / (total * 1.0));
					System.out.println("result = " + result);
				}
			} else {
				double temp = calcDiff(f,xVal,yVal);
				System.out.println("temp = " + temp);
				/**
				 * 如果field权重为1,那么相似度 = temp /权重为1的所有field数量
				 * 否则就按照权重进行分配。
				 * 例如:
				 */
				if (elem.weight() > 0 && elem.weight() < 1) {
					temp = temp * elem.weight();
				} else {
					temp = temp * restWeight / blankWeightFieldCount;
				}
//				System.out.println("temp = " + temp);
				result += temp;
				System.out.println("result = " + result);
			}
		}
		return String.format("%.2f",result);
	}
	
	private static double calcDiff(Field f,Object xVal, Object yVal) {
		long x = xVal.hashCode();
		long y = yVal.hashCode();
		System.out.println("x = " + x);
		System.out.println("y = " + y);
		Elem elem = f.getAnnotation(Elem.class);
		List<String> elemVals = Arrays.asList(elem.list());
		double temp = 0.0;
		switch(elem.type()) {
			case NUMBER:
				if (x == y) {
					temp =  0;
				} else {
					double max = x > y ? x : y;
					temp = Math.sqrt(EuclidDistance(x,y,1.0))/(max * 1.0);
					temp = Double.parseDouble(String.format("%.2f", temp));
				}
				break;
			case ERYUAN: temp = (x == y) ? 0 : 1; break;
			case BASIC: temp = (x == y) ? 0 : 1;break;
			case XUSHU:
				int xIndex = elemVals.indexOf(xVal);
				int yIndex = elemVals.indexOf(yVal);
				System.out.println("xIndex = " + xIndex);
				System.out.println("yIndex = " + yIndex);
				if (xIndex == -1) {
					throw new IllegalArgumentException("第一个对象的序数参数" + f.getName() + "值不匹配");	
				}
				if (yIndex == -1) {
					throw new IllegalArgumentException("第二个对象的序数参数" + f.getName() + " 值不匹配");	
				}
				temp = Math.abs(xIndex - yIndex)/(elemVals.size() * 1.0); break;
			default:
				break; 
		}
		return temp;
	}

	/**
	 * 欧几里得距离公式
	 * @param x0
	 * @param x1
	 */
	private static double EuclidDistance(long x0, long x1,double weight){
		return Math.pow(Math.abs(x0-x1), 2) * weight;
	}
	
	/**
	 * 数据统计
	 */
	private static <T extends TestObject> List<MyStatistics> getMyStatistics(T... objList){
		Map<String, List<MyHobby>> hobbyMap = new ConcurrentHashMap<>();
		Map<String, Integer> countMap = new ConcurrentHashMap<>();
		List<MyStatistics> myStatistics = new ArrayList<>();
		for (T obj: objList) {
			String gender = obj.getGender();
			countMap.put(gender,countMap.get(gender)== null ? 1 : countMap.get(gender) + 1);
			if (hobbyMap.get(gender) == null) {
				List<MyHobby> myHobbyList = obj.getMyHobby();
				hobbyMap.put(gender,myHobbyList);
				System.out.println(gender);
				System.out.println(myHobbyList.toString());
			} else {
				List<MyHobby> mapList = hobbyMap.get(gender);
				List<MyHobby> myHobbyList = obj.getMyHobby();
				for (MyHobby myHobby: myHobbyList) {
					if (!mapList.contains(myHobby)) {
						mapList.add(myHobby);
					}
				}
				hobbyMap.put(gender,mapList);
			}
		}
		for (String gender: hobbyMap.keySet()) {	
			myStatistics.add(new MyStatistics().setGender(gender)
					.setMyHobbyList(hobbyMap.get(gender)).setNbPerson(countMap.get(gender)));
		}
 		return myStatistics;
	}

	
	public static void main(String[] args) {
		DiffUtils util = new DiffUtils();
		MyHobby h1 = new MyHobby("爬山");
		MyHobby h2 = new MyHobby("音乐");
		MyHobby h3 = new MyHobby("看书");
		MyHobby h4 = new MyHobby("追剧");
		MyHobby h5 = new MyHobby("摄影");
		MyHobby h6 = new MyHobby("户外运动");
		List<MyHobby> list1 = new ArrayList<>();
		list1.add(h1);
		list1.add(h2);
		list1.add(h3);
		list1.add(h4);
		list1.add(h5);
		List<MyHobby> list2 = new ArrayList<>();
		list2.add(h1);
		list2.add(h2);
		list2.add(h3);
		list2.add(h4);
		List<MyHobby> list3 = new ArrayList<>();
		list3.add(h6);
		list3.add(h2);
		List<String> characterList1 = new ArrayList<>();
		characterList1.add("坚强");
		characterList1.add("自信");
		characterList1.add("乐观");
		List<String> characterList2 = new ArrayList<>();
		characterList2.add("坚强");
		characterList2.add("自信");
		characterList2.add("乐观");
		TestObject obj1 = util.new TestObject("zyg",18,"男",list1,characterList1);
		TestObject obj2 = util.new TestObject("zcx",15,"女",list2,characterList2);
		TestObject obj3 = util.new TestObject("zyg",22,"男",list3,characterList1);
		System.out.println(getMyStatistics(obj1,obj2,obj3));
		try {
			double res = Double.parseDouble(calculDiff(obj1,obj2));
			System.out.println("相似度:" + (1-res));
			System.out.println(calculDiff(obj1,obj2,obj3));
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

—————————————— 有问题的话评论区见——————————————

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zygswo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值