Дольше, чем я ожидал:
>>> df
samples subject trial_num
0 [-0.07, -2.9, -2.44] 1 1
1 [-1.52, -0.35, 0.1] 1 2
2 [-0.17, 0.57, -0.65] 1 3
3 [-0.82, -1.06, 0.47] 2 1
4 [0.79, 1.35, -0.09] 2 2
5 [1.17, 1.14, -1.79] 2 3
>>>
>>> s = df.apply(lambda x: pd.Series(x['samples']),axis=1).stack().reset_index(level=1, drop=True)
>>> s.name = 'sample'
>>>
>>> df.drop('samples', axis=1).join(s)
subject trial_num sample
0 1 1 -0.07
0 1 1 -2.90
0 1 1 -2.44
1 1 2 -1.52
1 1 2 -0.35
1 1 2 0.10
2 1 3 -0.17
2 1 3 0.57
2 1 3 -0.65
3 2 1 -0.82
3 2 1 -1.06
3 2 1 0.47
4 2 2 0.79
4 2 2 1.35
4 2 2 -0.09
5 2 3 1.17
5 2 3 1.14
5 2 3 -1.79
Если вам нужен последовательный индекс, вы можете применить reset_index(drop=True)
к результату.
update:
>>> res = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack()
>>> res = res.reset_index()
>>> res.columns = ['subject','trial_num','sample_num','sample']
>>> res
subject trial_num sample_num sample
0 1 1 0 1.89
1 1 1 1 -2.92
2 1 1 2 0.34
3 1 2 0 0.85
4 1 2 1 0.24
5 1 2 2 0.72
6 1 3 0 -0.96
7 1 3 1 -2.72
8 1 3 2 -0.11
9 2 1 0 -1.33
10 2 1 1 3.13
11 2 1 2 -0.65
12 2 2 0 0.10
13 2 2 1 0.65
14 2 2 2 0.15
15 2 3 0 0.64
16 2 3 1 -0.10
17 2 3 2 -0.76
Функции возвращают неизменные значения. Это так, как это происходит в Swift. Если вы хотите, чтобы он был изменяемым, вы должны сначала сохранить его в var
.
Однако, вы можете использовать +
для объединения Array
с любым Sequence
. Поэтому, если filteredApps
является Array
, это должно сработать:
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? self.timeSeriesData(appData: data) : nil
} + self.locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? self.timeSeriesData(locationData: data) : nil
}
let filteredData = filteredDataOpt.compactMap { [110] }
Есть несколько других вещей, которые мы можем сделать, чтобы очистить и этот код. Мы можем вычленить тест даты:
func isValid(_ candidate: Date) -> Bool {
return dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: candidate, toGranularity: Calendar.Component.day)
}
}
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
return isValid(data.date) ? self.timeSeriesData(appData: data) : nil
} + self.locationsData.map { data in
return isValid(data.date) ? self.timeSeriesData(locationData: data) : nil
}
let filteredData = filteredDataOpt.compactMap { [111] }
В зависимости от ваших данных, может быть лучше предварительно рассчитать допустимые диапазоны Date
:
let calendar = Calendar.current
let dayRanges: [Range<Date>] = dates.lazy.compactMap({ [112] }).map({ date in
let start = calendar.startOfDay(for: date)
let end = calendar.date(byAdding: .day, value: 1, to: start)!
return start ..< end
})
func isValid(_ candidate: Date) -> Bool {
return dayRanges.contains(where: { [112].contains(candidate) })
}
Мы также можем разделить фильтрация от трансформера. Это позволяет нам исключить использование compactMap
:
let filteredData = Array(filteredApps.lazy.filter({ isValid([113].date) }).map(self.timeSeriesData))
+ locationsData.lazy.filter({ isValid([113].date) }).map(self.timeSeriesData)
Или мы могли бы использовать compactMap
дважды:
let filteredData = filteredApps.compactMap({ isValid([114].date) ? self.timeSeriesData(appData: [114]) : nil })
+ locationsData.compactMap({ isValid([114].date) ? self.timeSeriesData(locationData: [114]) : nil })
Ваша проблема может быть сведена к следующему:
let data = [1, 2, 3]
let data2 = [4, 5, 6]
let filteredData: [Int] = data
.map { [110] }
.append(contentsOf: data2.map { [110] })
Решение состоит в том, чтобы использовать конкатенацию вместо append
:
let data = [1, 2, 3]
let data2 = [4, 5, 6]
let filteredData: [Int] = data
.map { [111] }
+ data2.map { [111] }
Для объяснения это похоже на :
let a: Int = 0
let b = a += 1 // this is append
let c = (a + 1) += 1 // this is append with a temporary expression
(вы добавляете что-то, что немедленно отбрасывается, а значение не сохраняется в c
).
, что, очевидно, следует сделать как
let a: Int = 0
let b = a + 1
. Обратите внимание, что даже если бы вы могли append
использовать временное возвращаемое значение, append
не имеет возвращаемого значения, а ваш результат, назначенный filteredDataOpt
, быть Void
.
Причина, по которой временные выражения являются постоянными (неизменяемыми), заключается в том, чтобы не допустить подобных ошибок.
Проблема в том, что метод append(contentsOf:)
мутирует, и возвращаемый элемент любой функции в swift по умолчанию неизменен.
Вот почему вы не можете вызвать метод append(contentsOf:)
для массива, возвращенного методом map
.
Лучше вы можете использовать не мутантный метод appending(contentsOf:)
для своего кода.
Таким образом, ваш код будет:
// next, select only entries in range
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromAppData(data) : nil
}.appending(contentsOf: locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromLocationData(data) : nil
})
Не ответ на ваш вопрос, но это сработает
var filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromAppData(data) : nil
}
filteredDataOpt.append(contentsOf: locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromLocationData(data) : nil})