По сути, вы должны создать выражение Column
, которое приведёт ваш ввод к типу с очищенными именами полей. Для этого вы можете использовать функцию org.apache.spark.sql.functions.struct
, которая позволяет комбинировать другие Column
для создания столбца структурного типа. Нечто подобное должно работать:
import org.apache.spark.sql.{functions => f}
def sanitizeName(s: String): String = s.replace(" ", "_")
def sanitizeFieldNames(st: StructType, context: String => Column): Column = f.struct(
st.fields.map { sf =>
val sanitizedName = sanitizeName(sf.name)
val sanitizedField = sf.dataType match {
case struct: StructType =>
val subcontext = context(sf.name)
sanitizeFieldNames(struct, subcontext(_))
case _ => context(sf.name)
}
sanitizedField.as(sanitizedName)
}: _*
)
Вы используете это так:
val df: DataFrame = ...
val appFieldType = df.schema("app").asInstanceOf[StructType] // or otherwise obtain the field type
df.withColumn(
"app",
sanitizeFieldNames(appFieldType, df("app")(_))
)
Для вашего типа эта рекурсивная функция будет возвращать столбец типа
f.struct(
df("app")("environment").as("environment"),
df("app")("name").as("name"),
f.struct(
df("app")("type")("word tier").as("word_tier"),
df("app")("type")("level").as("level")
).as("type")
)
[1120 ], который затем присваивается полю «app», заменяя то, что там присутствует.
Однако у этого решения есть ограничение. Он не поддерживает вложенные массивы или карты: если у вас есть схема со структурами внутри массивов или карт, этот метод не будет преобразовывать какие-либо структуры внутри массивов и карт. Тем не менее, в Spark 2.4 они добавили функции, которые выполняют операции с коллекциями, поэтому возможно, что в Spark 2.4 эта функция может быть обобщена для поддержки вложенных массивов и карт.
Наконец, можно делать то, что вы хотите с mapPartitions
. Во-первых, вы пишете рекурсивный метод, который очищает только StructType
вашего поля:
def sanitizeType(dt: DataType): DataType = dt match {
case st: StructType => ... // rename fields and invoke recursively
case at: ArrayType => ... // invoke recursively
case mt: MapType => ... // invoke recursively
case _ => dt // simple types do not have anything to sanitize
}
Во-вторых, вы применяете очищенную схему к вашему фрейму данных. Есть два основных способа сделать это: безопасный mapPartitions
и один, который использует внутренний Spark API.
С mapPartitions
это просто:
df.mapPartitions(identity)(RowEncoder(sanitizeType(df.schema)))
Здесь мы применяем операцию mapPartitions
и явно указываем выходной кодер. Помните, что схемы в Spark не свойственны данным: они всегда связаны с конкретным кадром данных. Все данные внутри фрейма данных представлены в виде строк без меток на отдельных полях, только позиции. Пока ваша схема имеет одинаковые типы на тех же позициях (но с потенциально разными именами), она должна работать так, как вы ожидаете.
mapPartitions
действительно приводит к нескольким дополнительным узлам в логическом плане. Чтобы избежать этого, можно создать экземпляр Dataset[Row]
непосредственно с помощью конкретного кодировщика:
new Dataset[Row](df.sparkSession, df.queryExecution.logical, RowEncoder(sanitizeType(df.schema)))
Это позволит избежать ненужного mapPartitions
(что, как правило, приводит к десериализации-map-serialize шаги в плане выполнения запроса), но это может быть небезопасно; Я лично не проверял это сейчас, но это могло сработать для вас.
Если Вы захотите, чтобы IE был быстр - или обычно рассматривал скорость, то Вы захотите создать фрагмент DOM сначала прежде, чем вставить его.
John Resig объясняет технику и включает сравнительный тест производительности:
http://ejohn.org/blog/dom-documentfragments/
var i = 10,
fragment = document.createDocumentFragment(),
div = document.createElement('div');
while (i--) {
fragment.appendChild(div.cloneNode(true));
}
$('#container').append(fragment);
Я понимаю, что это не делает большое использование jQuery в создании фрагмента, но я запустил несколько тестов, и я, может казаться, не делаю $ (фрагмент) .append () - но я читал, выпуск jQuery 1.3 John's, как предполагается, включает изменения на основе исследования, связанного выше.
Самый быстрый способ сделать это должно создать содержание сначала со строками. Это НАМНОГО быстрее для работы со строками для создания фрагмента документа прежде, чем работать с DOM или jQuery вообще. К сожалению, IE делает действительно плохое задание конкатенации строк, таким образом, лучший способ сделать это состоит в том, чтобы использовать массив.
var cont = []; //Initialize an array to build the content
for (var i = 0;i<10;i++) cont.push('<div>bunch of text</div>');
$('#container').html(cont.join(''));
Я использую эту технику тонна в моем коде. Можно создать очень большие фрагменты HTML с помощью этого метода, и это очень эффективно во всех браузерах.
Можно перенести собственный массив JavaScript в jQuery и использовать карту () для преобразования ее в jQuery (список узлов DOM). Это официально поддерживается.
$(['plop', 'onk', 'gloubi'])
.map(function(i, text)
{
return $('<div/>').text(text).get(0);
})
.appendTo('#container');
Это создаст
<div id="container">
<div>plop</div>
<div>onk</div>
<div>gloubi</div>
</div>
Я часто использую эту технику, чтобы не повторять меня (DRY).
Можно использовать регулярный цикл с JQuery, добавляют функцию.
for(i=0;i<10; i++){
$('#container').append("<div></div>");
}