将特定数据段从.dat文件转换为带有时间戳的CSV格式

2jcobegt  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(101)

我有一个.dat文件,其中包含以类似于下面的格式结构化的数据段:

$
GPS LAT GPS LONG   ROLL  PITCH AZIMUT   T IN  PmBARS  T OUT    RH%
8.9636   77.7201   -0.5    1.0    0.0   43.3   998.7   25.1   86.0                    

   BL#  MONTH   DAY  YEAR  HOUR   MIN  VAL1  VAL2  VAL3  VAL4
 19753    12     7  2023     0     0    73    82   121     0

  SPU1  SPU2  SPU3  SPU4 NOIS1 NOIS2 NOIS3 NOIS4 FEMAX SOFTW
     1     0     0     0  4203  4102  4700    18   503  9065

  FE11  FE12  FE21  FE22  SNR1  SNR2  SNR3  SNR4 CHECK   JAM
     8     7     8     8   139   139   139     0    40 11100

   ALT    CT SPEED   DIR     W    SW    SU    SV  ETAM

   200    46   299    46     0     1    19    13     0
   190    52   288    46     0     1    18    15     0
   180    58 -9999    47     0     1    18    17     0
   ...   ...   ...   ...   ...   ...   ...   ...   ...
   30    975    29    73    -8     1    14     2     0 
$

  GPS LAT  GPS LONG   ROLL  PITCH AZIMUT   T IN  PmBARS  T OUT    RH%
    8.9636   77.7201   -1.0    0.9    0.0   48.5   997.7   28.2   73.2                    

   BL# MONTH   DAY  YEAR  HOUR   MIN  VAL1  VAL2  VAL3  VAL4
 19944    12     7  2023    0     10    21    34    51     0

  SPU1  SPU2  SPU3  SPU4 NOIS1 NOIS2 NOIS3 NOIS4 FEMAX SOFTW
     1     0     0     0  5502  5403  5701     8   490  9065

  FE11  FE12  FE21  FE22  SNR1  SNR2  SNR3  SNR4 CHECK   JAM
     8     7     9     7   135   136   139     0    56 11100

   ALT    CT SPEED   DIR     W    SW    SU    SV  ETAM

   200    66   598    39    -9    16    22    36   169
   190    72   599    39    -9    16    22    36   189
   180    77   600    38    -9    17    22    36   194
    ..    ..   ...    ..     .    ..    ..    ..   ...
    20    80   600    37    -9    15    22    35   154
    30    83   591    36    -9    14    22    35   115

$

Again the same above format only the data will change. This dataset contains multiple segments similar to the provided snippet, each representing a 10-minute interval of records between each $.

$

字符串
我需要帮助将此数据转换为CSV文件名为“输出.csv”与特定格式:
1.第一行包含如下指定的标题。
1.后续行应包含:

  • 从XML中提取的YYYY-MM-DD HH:MM:SS格式的时间戳。
  • 不同高度(200米至30米)的风速。
  • 不同高度(200米至30米)的风向。

例如,对于给定的输入,所需的输出CSV将类似于:

Timestamp, ALT200_Speed, ALT190_Speed, ..., ALT30_Speed, ALT200_Dir, ALT190_Dir, ..., ALT30_Dir

2023-12-07 00:00:00, 299, 288, ..., 29, 46, 46, ..., 73
2023-12-07 00:00:10, 598, 599, ..., 591, 39, 39, ..., 36


我寻求Python或任何其他合适语言的指导或代码解决方案,以有效地处理这个大型数据集并生成包含指定信息的所需输出CSV。
读取.dat文件并提取相关数据段
遍历文件,识别并提取包含$ symbol之间的风数据的段
处理每个片段以提取时间戳、风速和方向
生成具有适当标头的“output.csv”并写入数据
将每个时间戳、风速和方向以指定格式写入CSV文件

1mrurvl1

1mrurvl11#

您可以使用一个简单的脚本来处理这个问题,我看到了一系列的块,其中每个块都有一个标头,后面是测量数据行;块用$分隔。
每个标题都有一些文本,可以很容易地格式化为时间戳值看起来是统一的,所以简单的文本转换就足够了;不需要创建一个datetime obj然后格式化它(但是如果你喜欢的话,你可以)。
测量数据行需要被“压缩”成一行,每个高度都有速度和方向的测量元组。如果你的真实的数据在一个块中有一个高度,而其他块中没有,我将使用dict和csv.DictWriter来优雅地填充不存在的值对。
我将从一个helper函数开始,它将一个文本行拆分为一个strs列表:

def to_row(line: str) -> list[str]:
    """Break-up a line of (fixed-width) space-delimited text into a row of fields."""
    row = [x.strip() for x in re.split(r"\s+", line)]

    return row[1:]  # trim empty field that was leading space

字符串
尝试一下:

print(to_row("  Foo     Bar Baz"))
print(to_row("   19      1  0.1"))
['Foo', 'Bar', 'Baz']
[ '19',   '1', '0.1']

然后打开DAT文件并将其拆分为块:

# split on $, and trim whitespace around each chunk
# might need to change LE to \r\n, or even just \r
LE = "\n"
with open("input.dat") as f:
    txt_chunks = f.read().split("$" + LE)
    txt_chunks = txt_chunks[1:]  # trim leading blank from $
    chunks = [x.strip().split(LE) for x in txt_chunks]


中间值将存储在一个我称之为“表”的字典列表中:

# parse relevant lines in each chunk into something like the
# following (one dict-item per chunk):
# [
#     {
#         "Timestamp":    "2023-01-01 00:00",
#         "ALT200_SPEED":               "299",
#         "ALT200_DIR":                  "46",
#         "ALT190_SPEED":               "288",
#         "ALT190_DIR":                  "46",
#         ...
#     },
#     {
#         "Timestamp":    "2023-01-01 00:10",
#         "ALT200_SPEED":              "598",
#         ...
#     },
#     ...
# ]
table: list[dict[str, str]] = []


然后将块中的每一行都传递给to_row帮助函数。我还Assert块中的行以确保所有内容都正确排列(也记录了哪些行是什么)。我还选择从文本中构建时间戳。我使用格式化填充指令:>02来填充除年份之外的所有内容:

for chunk in chunks:
    assert chunk[0].startswith("GPS LAT")

    assert chunk[3].startswith("   BL#")
    header = to_row(chunk[4])
    mo, day, yr = header[1:4]
    hr, minute = header[4:6]
    ts = f"{yr}-{mo:>02}-{day:>02} {hr:>02}:{minute:>02}:00"

    final_row: dict[str, str] = {
        "Timestamp": ts,
    }

    assert chunk[12].startswith("   ALT")
    for line in chunk[14:]:
        _row = to_row(line)

        if len(_row) == 0 or _row[0][:2] == "..":
            continue

        alt = _row[0]

        if int(alt) < 30:
            continue

        speed = _row[2]
        dir = _row[3]

        final_row[f"ALT{alt}_SPEED"] = speed
        final_row[f"ALT{alt}_DIR"] = dir

    table.append(final_row)

从行列表中,您需要所有唯一的字段名称:

all_keys = {k for row in table for k in row}


您的示例输出对最终字段名有特定的排序顺序。以下键func不会为特殊的Timestamp名称返回任何特定值。所有其他名称都将转换为元组,如(1, 200)表示“ALT200_SPEED”,(0, 200)表示“ALT200_SPEED”:

def col_name_key(k: str) -> tuple[int, int]:
    if k == "Timestamp":
        return (0, 0)

    k = k[3:]  # trim "ALT"
    parts = k.split("_")
    val, kind = int(parts[0]), parts[1]

    return (1 if kind == "SPEED" else 0, val)

fieldnames = sorted(all_keys, key=col_name_key, reverse=True)

这将导致一个中间排序顺序,看起来像这样:

[
    (190, 0),  # "ALT190_DIR"
    (190, 1),  # "ALT190_SPEED"
    (200, 0),  # "ALT200_DIR"
    (200, 1),  # "ALT200_SPEED"
]

在被反转成最终顺序之前:

["ALT200_SPEED", "ALT200_DIR", "ALT190_SPEED", "ALT190_DIR"]

作为一个特殊的步骤,确保Timestamp是第一个:

fieldnames.remove("Timestamp")
fieldnames.insert(0, "Timestamp")

然后将所有内容写入CSV:

with open("output.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=fieldnames, restval="N/A")
    writer.writeheader()
    writer.writerows(table)
Timestamp,ALT200_SPEED,ALT190_SPEED,ALT180_SPEED,ALT30_SPEED,ALT200_DIR,ALT190_DIR,ALT180_DIR,ALT30_DIR
2023-12-07 00:00:00,299,288,-9999,29,46,46,47,73
2023-12-07 00:10:00,598,599,600,591,39,39,38,36

restval="N/A"确保所有行中不存在的任何字段名都将得到妥善处理。想象一下,如果您在上面的代码中没有按海拔低于30米进行过滤。您将有一行具有“ALT 20_...”列。该restval将简单地将N/A插入任何没有这些列的行,如:

Timestamp,ALT200_SPEED,ALT190_SPEED,ALT180_SPEED,ALT30_SPEED,ALT20_SPEED,ALT200_DIR,ALT190_DIR,ALT180_DIR,ALT30_DIR,ALT20_DIR
2023-12-07 00:00:00,299,288,-9999,29,N/A,46,46,47,73,N/A
2023-12-07 00:10:00,598,599,600,591,600,39,39,38,36,37

相关问题