Python大数据分析11:子查询

所谓子查询,就是指利用其他查询的结果作为当前查询的条件,这在我们日常数据查询中非常有用。

比如我们想查询最高身高的学生姓名。我们来看看,这个问题可否一次查询出来?要想查询这个学生姓名,首先需要知道最高的身高,但是最高的身高是多少?这个仍然需要查询。显然,这是个子查询。

我们不妨就按照刚才这个思路来做。首先查询最高身高:

# coding:utf-8
import pandas as pd

data = {'ID': ['000001', '000002', '000003', '000004', '000005', '000006', '000007'],
        'name': ['黎明', '赵怡春', '张富平', '白丽', '牛玉德', '姚华', '李南'],
        'gender': [True, False, True, False, True, False, True],
        'age': [16, 20, 18, 18, 17, 18, 16],
        'height': [1.88, 1.78, 1.81, 1.86, 1.74, 1.75, 1.76]
        }
frame = pd.DataFrame(data)
print(frame['height'].max())

语句并不复杂,就是查询身高列的最大值。

然后我们根据这个最大值再去查询相应的学生姓名:

print(frame[frame[‘height’] == frame[‘height’].max()])

这里我们将刚才查询出来的最高身高作为当前查询的条件,继续查询学生姓名。

当然,为了能看清语句,我们也可以这样写:

maxHeight = frame[‘height’].max()

print(frame[frame[‘height’] == maxHeight])

这里将刚才的最高身高保存到一个变量中,更方便阅读和理解,效果一样。

有时问题会复杂一些,比如我们想查询最高身高的女生姓名。在这里,范围限定在女生,因此最高身高必须要从女生中来查询:

print(frame[frame[‘gender’] == False][‘height’].max())

这里我们增加了选择条件,限定了查询范围。

然后就可以根据这个身高来查询,但是同样仍然要注意要在女生中再次选择。

maxHeight = frame[frame[‘gender’] == False][‘height’].max()

print(frame[(frame[‘gender’] == False) & (frame[‘height’] == maxHeight)])

这里最后我们通过并且的关系确保女生和最高身高两个条件同时被满足。

这里的子查询都是返回一个结果,比如最高的身高,但是更为常见的情况是返回可能更多的结果。

比如我们来查询下前两种最高身高相关的学生姓名。首先需要得到最高的前两种身高:

maxHeight = frame[‘height’].sort_values(ascending=False).head(2)

print(maxHeight)

这里不能使用max,可以考虑采取降序排序的方法,再取前两个数值。结果看起来很不错,不过,大家想一想这样做有没有问题?

是的,如果有两个1.88的学生会怎么样?显然,我们需要将相同身高归入一类。

maxHeight = frame[‘height’].drop_duplicates().sort_values(ascending=False).head(2)

print(maxHeight)

此时即使有相同的最高身高,也不会重复显示,drop_duplicates表示去除重复元素,只保留每种身高。

接下来,我们就可以将等于这些身高的所有学生查询出来:

print(frame[frame[‘height’].isin(maxHeight)])

这里由于是前两种最高身高,不是一个唯一的最高值,因此不能采取相等判断,而是使用isin表示是否在这些身高中。

结果可以看出,有三位同学,因为有的身高可能有多位同学都满足。

子查询通常更多的应用于对于多个DataFrame的查询。比如我们想知道黎明的所有选修课程成绩。从现有的DataFrame来看,学生信息和成绩信息分别在两个不同的DataFrame中,

# coding:utf-8
import pandas as pd

data1 = {'ID': ['000001', '000002', '000003', '000004', '000005', '000006', '000007'],
         'name': ['黎明', '赵怡春', '张富平', '白丽', '牛玉德', '姚华', '李南'],
         'gender': [True, False, True, False, True, False, True],
         'age': [16, 20, 18, 18, 17, 18, 16],
         'height': [1.88, 1.78, 1.81, 1.86, 1.74, 1.75, 1.76]
         }
students = pd.DataFrame(data1)
data2 = {
    'SID': ['000001', '000001', '000002', '000003', '000003', '000003', '000004', '000004', '000005', '000006','000006'],
    'CID': ['A01', 'A02', 'A01', 'A01', 'A02', 'B01', 'A01', 'A03', 'B01', 'A02', 'B01'],
    'score': [56, 78, 90, 74, 86, 89, 67, 80, 77, 76, 90]
}
scores = pd.DataFrame(data2)

我们可以从结果反过来设想下,在scores中有所有学生的课程成绩,但是只能通过学号ID来查询,而我们只有黎明这个信息,所以我们必须先查询黎明的学号。显然,这又是一个子查询问题。

首先先获取黎明的学号:

print(students[students[‘name’] == ‘黎明’][‘ID’])

这只是个简单的行列选择。这里大家要注意,虽然此时返回的只有一个数值,但是从理论上说,也有可能会有多个数值结果,因此返回的结果是个序列,虽然此时该序列只有一个数值。

然后再根据这个ID去scores数据框架中查询对应的课程成绩。

print(scores[scores[‘SID’] == sid[0]])

这里要注意一个小问题,sid后面需要增加一个序号来得到第一个元素。

当然考虑到刚才第一个查询返回的结果可能有多个数值,更为灵活的写法还是使用isin:

print(scores[scores[‘SID’].isin(sid)])

不管一个还是多个,这种写法始终有效。

这里可能有学生也会说,好像通过连接也可以做,确实如此:

frame = pd.merge(students, scores, left_on=’ID’, right_on=’SID’)

print(frame[frame[‘name’] == ‘黎明’])

似乎更为简单,其实对于很多子查询,我们都可以考虑通过连接的方法来替换。

比如我们查询和黎明年龄一样的同学:

age = frame[frame[‘name’] == ‘黎明’][‘age’]

print(frame[(frame[‘age’].isin(age)) & (frame[‘name’] != ‘黎明’)][‘name’])

这里的操作和前面所讲区别不大,只是由于我们只想查询其他同学,于是增加了对黎明记录本身的过滤,

我们再次使用连接完成下:

frame = pd.merge(frame, frame, left_on=’age’, right_on=’age’)

print(frame[(frame[‘name_x’] == ‘黎明’) & (frame[‘name_y’] != ‘黎明’)][‘name_y’])

连接未必总是通过学号等关键列来进行,完全可以根据需要自由选择。比如这里我们按照年龄连接,可以想象,身高一样的都会连接起来,形成一个很大的表格。然后在其中,选择一半为黎明另一半不是黎明的另一半数据中的学生姓名。

发表评论

邮箱地址不会被公开。 必填项已用*标注