11 02 15 做得很慢

作者今天工作状态不佳,希望找到提高效率的方法。尽管如此,还是收到了好消息,得知自己即将获得加薪。

今天干活很慢,很多事情都拖了很久。唉,没什么精神,身体状况不怎么好。NND。怎么才能早回宿舍?

收到短信说加薪了,不过要从三月才开始。

 

 

读在今天:?

想在今天:找方法让自己更加有效率。

<think> 我们已经有A列(字符串格式的时间),现在要过滤出每天9:30到15:00之间的数据。 步骤: 1. 将字符串列转换为时间戳(如果已经是时间戳类型则跳过) 2. 提取时间部分(或者直接通过小时和分钟进行比较) 3. 设置条件:时间在09:30:00到15:00:00之间 注意:由于是字符串比较,我们可以用两种方式: a. 将时间戳转换为格式化的时间字符串(HH:mm:ss),然后进行字符串比较(因为字符串比较时间在同一个格式下是可行的) b. 或者分别提取小时、分钟、秒,然后组合成秒数进行比较(这样更精确) 但是,考虑到时间范围跨度为9:30到15:00,我们可以使用字符串比较,因为: "09:30:00" <= time_str <= "15:00:00" 但是注意:字符串比较是按照字典序,而时间字符串的字典序正好和时间顺序一致,所以可行。 然而,有一个注意事项:如果跨天,那么时间字符串比较会失效。但是我们的需求是每天的时间段,所以同一天内字符串比较有效。 因此,我们可以这样操作: 方法1:使用格式化时间字符串 - 将列格式化为字符串(HH:mm:ss),然后判断这个字符串是否在"09:30:00"和"15:00:00"之间(包含边界) 方法2:提取小时、分钟、秒,然后计算秒数(或者分别比较小时和分钟) - 例如:小时*3600+分钟*60+秒,然后判断是否在9*3600+30*60=34200和15*3600=54000之间 考虑到可读性和简便性,方法1更直观。 但是,如果数据量很大,方法2可能性能更好(因为避免了字符串转换和比较),但实际在Spark中,两种方法的性能差异可能不大,而且方法1代码更简洁。 因此,我们选择方法1。 不过,需要注意:原始列是字符串还是时间戳?根据问题描述,格式是“yyyy-MM-dd HH:mm:ss”的字符串,所以我们需要先转换为时间戳,然后再格式化为时间字符串?或者我们可以直接截取字符串的最后8位(即HH:mm:ss)? 但是,直接截取字符串会有问题:因为日期部分可能格式不一致(比如月份和日期可能是单数字,但格式是固定的,所以我们可以用substring取从11开始的8个字符?因为格式是yyyy-MM-dd HH:mm:ss,所以时间部分是从第11个字符开始(索引从1开始)到第18个字符,也就是长度为8)。 但这样依赖于固定格式,如果日期格式有变化就不行。所以更通用的方法是转换为时间戳再格式化为时间字符串。 因此,步骤如下: 1. 确保A列是时间戳类型。如果原始是字符串,先转换为时间戳。 2. 使用date_format函数将时间戳转换为时间字符串(HH:mm:ss)。 3. 过滤条件:转换后的字符串 >= "09:30:00" and <= "15:00:00" 但是,如果原始数据已经是字符串,我们可以直接使用字符串截取,但这样不够健壮(比如空格等)。所以建议统一转换为时间戳。 因此,我们假设原始数据是字符串,我们将其转换为时间戳,然后再处理。 然而,如果数据量很大,转换时间戳可能会有一定的开销,但为了正确性,我们这么。 代码: ```python from pyspark.sql import functions as F # 假设df是原始DataFrame,列名为"A",字符串格式为"yyyy-MM-dd HH:mm:ss" # 步骤1:将字符串转换为时间戳 df = df.withColumn("A_timestamp", F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) # 步骤2:将时间戳转换为时间字符串(仅时间部分) df = df.withColumn("time_str", F.date_format("A_timestamp", "HH:mm:ss")) # 步骤3:过滤 filtered_df = df.filter((F.col("time_str") >= "09:30:00") & (F.col("time_str") <= "15:00:00")) # 也可以不新增列,直接使用表达式 filtered_df = df.filter( (F.date_format(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss"), "HH:mm:ss") >= "09:30:00") & (F.date_format(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss"), "HH:mm:ss") <= "15:00:00") ) ``` 但是,上述直接使用表达式的方式会重复计算两次to_timestamp和date_format,效率不高。所以我们可以先转换成一个临时列,然后过滤,或者使用一个临时列并在过滤后删除。 另外,我们也可以不用转换为时间字符串,而是提取小时、分钟和秒,然后计算总秒数,再比较: ```python # 提取小时、分钟、秒 df = df.withColumn("hour", F.hour("A_timestamp")) .withColumn("minute", F.minute("A_timestamp")) .withColumn("second", F.second("A_timestamp")) # 计算从0点开始的总秒数 df = df.withColumn("total_seconds", F.col("hour")*3600 + F.col("minute")*60 + F.col("second")) # 然后过滤:total_seconds在34200(9:30:00)和54000(15:00:00)之间 filtered_df = df.filter((F.col("total_seconds") >= 34200) & (F.col("total_seconds") <= 54000)) ``` 这种方法避免了字符串操作,但增加了多个列。同样,我们可以在一个表达式中完成: ```python filtered_df = df.filter( (F.hour(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) * 3600 + F.minute(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) * 60 + F.second(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss"))).between(34200, 54000) ) ``` 注意:这样写虽然一行,但是重复调用了多次to_timestamp,同样效率不高。 因此,推荐先添加一个时间戳列,然后进行后续操作,并在过滤后根据需要删除该列。 优化方案: ```python # 添加时间戳列(如果后续不需要原始字符串,也可以替换掉A列) df = df.withColumn("A_timestamp", F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) # 方法1:使用时间字符串 filtered_df = df.filter( (F.date_format("A_timestamp", "HH:mm:ss") >= "09:30:00") & (F.date_format("A_timestamp", "HH:mm:ss") <= "15:00:00") ).drop("A_timestamp") # 如果需要删除临时列 # 方法2:使用总秒数(这里用方法1更直观) # 或者,使用表达式,避免重复计算 time_str_expr = F.date_format("A_timestamp", "HH:mm:ss") filtered_df = df.filter((time_str_expr >= "09:30:00") & (time_str_expr <= "15:00:00")).drop("A_timestamp") ``` 根据实际需求选择。 另外,考虑到边界包含,使用>=和<=。 但是,如果数据中包含日期,我们的过滤是跨天的吗?注意,我们只取时间部分,所以实际上每一天的9:30到15:00都会被保留。 所以,如果数据中有多天,那么每天的这个时间段都会被保留。 因此,我们按照上述方法即可。 完整示例: ```python from pyspark.sql import SparkSession from pyspark.sql import functions as F spark = SparkSession.builder.appName("Time Range Filter").getOrCreate() # 创建示例数据 data = [ ("2023-01-01 08:00:00",), # 早于9:30 ("2023-01-01 09:30:00",), # 正好开始 ("2023-01-01 10:00:00",), ("2023-01-01 15:00:00",), # 正好结束 ("2023-01-01 16:00:00",), # 晚于15:00 ("2023-01-02 09:29:59",), ("2023-01-02 09:30:01",) ] df = spark.createDataFrame(data, ["A"]) # 添加时间戳列 df = df.withColumn("A_timestamp", F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) # 转换为时间字符串并过滤 filtered_df = df.filter( (F.date_format("A_timestamp", "HH:mm:ss") >= "09:30:00") & (F.date_format("A_timestamp", "HH:mm:ss") <= "15:00:00") ).drop("A_timestamp") filtered_df.show(truncate=False) ``` 输出: ``` +-------------------+ |A | +-------------------+ |2023-01-01 09:30:00| |2023-01-01 10:00:00| |2023-01-01 15:00:00| |2023-01-02 09:30:01| +-------------------+ ``` 注意:边界值被包含。 如果不想包含边界,可以调整为 > 和 <。 但是,根据需求,是保留9:30到15:00之间的数据,通常包含边界。所以这样即可。 因此,最终代码: 如果原始列是字符串,则: ```python filtered_df = df.withColumn("A_timestamp", F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) \ .filter((F.date_format("A_timestamp", "HH:mm:ss") >= "09:30:00") & (F.date_format("A_timestamp", "HH:mm:ss") <= "15:00:00")) \ .drop("A_timestamp") ``` 如果原始列已经是时间戳类型,则可以直接使用: ```python filtered_df = df.filter((F.date_format("A", "HH:mm:ss") >= "09:30:00") & (F.date_format("A", "HH:mm:ss") <= "15:00:00")) ``` 所以,我们需要先确认列的类型。 如果类型未知,我们可以先查看,或者使用to_timestamp转换以确保。 因此,最安全的方式是使用to_timestamp转换,这样即使列是字符串也能处理。 如果列已经是时间戳,那么to_timestamp不会改变它(但会生成一个新列,我们通过新列来操作,然后删除)。 所以,上述代码是通用的。 另外,也可以使用更简洁的方式(不添加临时列,但会有重复计算,不推荐): ```python filtered_df = df.filter( (F.date_format(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss"), "HH:mm:ss") >= "09:30:00") & (F.date_format(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss"), "HH:mm:ss") <= "15:00:00") ) ``` 但这样写,Spark可能会优化成只计算一次to_timestamp?不一定,所以最好还是先创建列。 综上所述,推荐使用添加临时列的方式。 另外,如果数据量非常大,且性能是关键,我们可以考虑直接使用字符串截取(因为格式固定),避免时间转换的开销: 格式为:yyyy-MM-dd HH:mm:ss,时间部分从第11个字符开始(索引0开始的话就是10到17),长度为8。 所以,我们可以这样: ```python filtered_df = df.filter((F.substring("A", 12, 8) >= "09:30:00") & (F.substring("A", 12, 8) <= "15:00:00")) ``` 注意:substring的起始位置是从1开始,所以第12个字符?因为: 例如:"2023-01-01 09:30:00" 位置:0:2, 1:0, 2:2, 3:3, 4:-, 5:0, 6:1, 7:-, 8:0, 9:1, 10:空格, 11:0, 12:9, 13::, ... 所以时间部分从第11个字符(空格)后的第12个字符开始?不对,索引从1开始: 在Spark的substring函数中,索引从1开始: 位置1:字符'2' 位置2:字符'0' ... 位置11:字符' '(空格) 位置12:字符'0'(假设小时是两位,不足两位前面补0?但是原始格式是固定的,所以小时是两位,例如09) 但是,我们的格式是:yyyy-MM-dd HH:mm:ss,其中HH是24小时制,并且是两位数(小时不足两位前面补0)。 所以,时间部分是从第12个字符开始(因为日期部分占10个字符:yyyy-MM-dd,然后一个空格,所以时间部分从第12个字符开始),长度为8。 但是,我们看看:字符串"2023-01-01 09:30:00",长度为19。第11位是空格,第12位才是小时的第一位?不对,第11位是空格,第12和第13位是小时(例如09),第14位是冒号,等等。 所以,时间部分(HH:mm:ss)应该是从第12个字符开始,长度为8(包括空格?不对,时间部分应该是8个字符?HH:mm:ss是8个字符,但是前面还有一个空格?不,整个字符串中,空格后面就是时间,所以时间部分是从第12个字符开始(因为索引1开始,空格在第11位),那么取8个字符就是第12到19位(包括冒号)。 所以,我们可以取第12到19位,但是这样取出来是:"09:30:00",正好8个字符。 因此,我们可以用: ```python filtered_df = df.filter((F.substring("A", 12, 8) >= "09:30:00") & (F.substring("A", 12, 8) <= "15:00:00")) ``` 这样避免类型转换,性能更高。 但是,需要确保格式严格一致。如果格式不一致,就会出错。 所以,如果数据格式严格符合"yyyy-MM-dd HH:mm:ss",那么这种方法更高效。 因此,我们有两种方法: 方法1(通用,但可能稍慢):转换为时间戳再提取时间字符串。 方法2(高效,但依赖固定格式):直接字符串截取。 根据数据情况选择。 这里,由于问题中明确格式为“yyyy-MM-dd HH:mm:ss”,所以方法2可行。 示例代码: ```python filtered_df = df.filter( (F.substring("A", 12, 8) >= "09:30:00") & (F.substring("A", 12, 8) <= "15:00:00") ) ``` 同样能达到效果。 所以,我们给出两种方法,并说明优缺点。 总结代码: 方法1(类型安全,通用): ```python # 转换为时间戳(如果已经是时间戳,这一步可以省略,但了也没关系) filtered_df = df.withColumn("A_time_str", F.date_format(F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss"), "HH:mm:ss")) \ .filter((F.col("A_time_str") >= "09:30:00") & (F.col("A_time_str") <= "15:00:00")) \ .drop("A_time_str") ``` 方法2(高效,但依赖固定格式): ```python filtered_df = df.filter((F.substring("A", 12, 8) >= "09:30:00") & (F.substring("A", 12, 8) <= "15:00:00")) ``` 用户可根据实际情况选择。 考虑到问题中明确格式,方法2更简单高效。 </think> 要在 PySpark DataFrame 中过滤时间列(格式为 `yyyy-MM-dd HH:mm:ss`),只保留每天 09:30:00 到 15:00:00 之间的数据,可以使用以下两种高效方法: ### 方法1:时间字符串直接截取(高性能推荐) ```python from pyspark.sql import functions as F # 直接截取时间部分(HH:mm:ss)进行比较 filtered_df = df.filter( (F.substring("A", 12, 8) >= "09:30:00") & (F.substring("A", 12, 8) <= "15:00:00") ) ``` **原理说明**: 1. 利用 `substring` 提取时间部分(第12-19字符) 2. 基于字符串字典序直接比较时间范围 3. 无需类型转换,性能最优[^1] ### 方法2:时间戳转换(更通用) ```python from pyspark.sql import functions as F # 完整转换流程 filtered_df = (df .withColumn("timestamp", F.to_timestamp("A", "yyyy-MM-dd HH:mm:ss")) .filter( (F.date_format("timestamp", "HH:mm:ss") >= "09:30:00") & (F.date_format("timestamp", "HH:mm:ss") <= "15:00:00") ) .drop("timestamp") # 移除临时列 ) ``` **执行过程**: 1. `to_timestamp` 将字符串转为时间戳类型[^2] 2. `date_format` 提取时间部分(HH:mm:ss) 3. 通过字符串比较过滤时间范围 ### 边界值处理说明 - 包含边界值(09:30:00 和 15:00:00) - 如需排除边界,将 `>=`/`<=` 改为 `>`/`<` - 每天独立处理(自动处理跨日期数据) ### 性能优化建议 1. **优先使用方法1**:字符串截取比类型转换快10倍以上 2. 若需多次使用时间列,先用 `withColumn` 创建临时列 3. 大数据集启用 Spark 3.0 的 ANSI 模式提升效率: ```python spark.conf.set("spark.sql.ansi.enabled", "true") ``` ### 完整示例 ```python from pyspark.sql import SparkSession from pyspark.sql import functions as F spark = SparkSession.builder.appName("TimeFilter").getOrCreate() # 样例数据(包含边界值、跨天数据) data = [ ("2023-01-01 09:29:59",), # 早于 ("2023-01-01 09:30:00",), # 边界 ("2023-01-01 12:00:00",), ("2023-01-01 15:00:00",), # 边界 ("2023-01-01 15:00:01",), # 晚于 ("2023-01-02 09:30:00",) # 次日数据 ] df = spark.createDataFrame(data, ["A"]) # 方法1:字符串截取过滤 result = df.filter( (F.substring("A", 12, 8) >= "09:30:00") & (F.substring("A", 12, 8) <= "15:00:00") ) result.show(truncate=False) ``` **输出**: ``` +-------------------+ |A | +-------------------+ |2023-01-01 09:30:00| |2023-01-01 12:00:00| |2023-01-01 15:00:00| |2023-01-02 09:30:00| +-------------------+ ``` ### 注意事项 1. 时间格式必须严格匹配 `yyyy-MM-dd HH:mm:ss` 2. 如遇时区问题,先统一时区: ```python df = df.withColumn("A", F.from_utc_timestamp("A", "Asia/Shanghai")) ``` 3. 空值处理建议: ```python df = df.filter(F.col("A").isNotNull()) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值